第七篇:React前端架构:Vite + TypeScript + TailwindCSS工程化配置
·
📚 QuantumFlow工作流自动化从入门到精通 - 第7篇
前面六篇文章我们完成了后端核心功能的开发。从本篇开始,我们将构建一个现代化的前端应用。本文将搭建基于Vite + TypeScript + TailwindCSS的工程化前端架构,为后续的可视化流程编辑器打下坚实基础。
📋 本文概览
学习目标:
- 掌握Vite的快速开发体验和构建优化
- 学会TypeScript在React项目中的最佳实践
- 理解TailwindCSS的原子化CSS设计理念
- 掌握React Router v6的路由设计模式
- 对比Zustand和Redux,选择合适的状态管理方案
- 实现优雅的API请求封装和缓存策略
技术栈:
- Vite 5.0+
- React 18+
- TypeScript 5.0+
- TailwindCSS 3.4+
- React Router v6
- Zustand 4.0+
- Axios + React Query (TanStack Query)
预计阅读时间: 28分钟
前置知识: React基础、TypeScript基础、CSS基础
🎯 一、为什么选择这套技术栈?
1.1 技术选型对比
/**
* 前端技术栈选型决策
*/
// 构建工具对比
const buildToolComparison = {
"Vite": {
pros: [
"极速的冷启动(基于ESM)",
"HMR热更新快如闪电",
"开箱即用的TypeScript支持",
"生产构建基于Rollup,体积小"
],
cons: [
"生态相对较新",
"部分老旧浏览器需要polyfill"
],
verdict: "✅ 推荐用于新项目"
},
"Create React App": {
pros: [
"官方脚手架,稳定性高",
"配置简单,适合初学者"
],
cons: [
"启动速度慢(基于Webpack)",
"配置不灵活(需要eject)",
"构建速度慢"
],
verdict: "❌ 已不推荐"
},
"Next.js": {
pros: [
"SSR/SSG支持",
"文件路由系统",
"优秀的SEO"
],
cons: [
"学习曲线陡峭",
"对于纯SPA项目过于重量级"
],
verdict: "⚠️ 适合需要SSR的项目"
}
};
// 状态管理对比
const stateManagementComparison = {
"Zustand": {
size: "1.2KB (gzipped)",
complexity: "低",
boilerplate: "极少",
devtools: "支持",
typescript: "原生支持",
verdict: "✅ 推荐用于中小型项目"
},
"Redux Toolkit": {
size: "12KB (gzipped)",
complexity: "中",
boilerplate: "较少(相比原生Redux)",
devtools: "强大",
typescript: "支持",
verdict: "⚠️ 适合大型复杂项目"
},
"Recoil": {
size: "21KB (gzipped)",
complexity: "中",
boilerplate: "中等",
devtools: "基础支持",
typescript: "支持",
verdict: "⚠️ 生态相对较新"
}
};
1.2 性能对比数据
| 指标 | Vite | CRA | Next.js |
|---|---|---|---|
| 冷启动时间 | ~200ms | ~15s | ~3s |
| HMR更新 | <50ms | ~1s | ~500ms |
| 生产构建 | ~15s | ~45s | ~30s |
| 包体积 | 小 | 中 | 中 |
🏗️ 二、Vite项目初始化
2.1 创建项目
# 使用Vite创建React + TypeScript项目
npm create vite@latest quantumflow-frontend -- --template react-ts
cd quantumflow-frontend
# 安装依赖
npm install
# 启动开发服务器
npm run dev
2.2 项目结构设计
quantumflow-frontend/
├── public/ # 静态资源
│ └── logo.svg
├── src/
│ ├── api/ # API请求封装
│ │ ├── client.ts # Axios实例配置
│ │ ├── workflows.ts # 工作流API
│ │ └── auth.ts # 认证API
│ ├── components/ # 公共组件
│ │ ├── ui/ # UI基础组件
│ │ │ ├── Button.tsx
│ │ │ ├── Input.tsx
│ │ │ └── Modal.tsx
│ │ ├── layout/ # 布局组件
│ │ │ ├── Header.tsx
│ │ │ ├── Sidebar.tsx
│ │ │ └── Layout.tsx
│ │ └── common/ # 通用业务组件
│ │ ├── LoadingSpinner.tsx
│ │ └── ErrorBoundary.tsx
│ ├── features/ # 功能模块(按业务划分)
│ │ ├── workflows/
│ │ │ ├── components/
│ │ │ ├── hooks/
│ │ │ ├── types/
│ │ │ └── utils/
│ │ ├── auth/
│ │ └── dashboard/
│ ├── hooks/ # 全局自定义Hooks
│ │ ├── useAuth.ts
│ │ └── useDebounce.ts
│ ├── routes/ # 路由配置
│ │ ├── index.tsx
│ │ └── ProtectedRoute.tsx
│ ├── store/ # 状态管理
│ │ ├── authStore.ts
│ │ └── workflowStore.ts
│ ├── types/ # TypeScript类型定义
│ │ ├── api.ts
│ │ ├── workflow.ts
│ │ └── user.ts
│ ├── utils/ # 工具函数
│ │ ├── format.ts
│ │ └── validation.ts
│ ├── App.tsx # 根组件
│ ├── main.tsx # 入口文件
│ └── index.css # 全局样式
├── .eslintrc.cjs # ESLint配置
├── .prettierrc # Prettier配置
├── tailwind.config.js # TailwindCSS配置
├── tsconfig.json # TypeScript配置
├── vite.config.ts # Vite配置
└── package.json
2.3 Vite配置优化
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [
react({
// 启用React Fast Refresh
fastRefresh: true,
// Babel配置
babel: {
plugins: [
// 支持装饰器语法(可选)
['@babel/plugin-proposal-decorators', { legacy: true }]
]
}
})
],
// 路径别名
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components'),
'@api': path.resolve(__dirname, './src/api'),
'@hooks': path.resolve(__dirname, './src/hooks'),
'@store': path.resolve(__dirname, './src/store'),
'@types': path.resolve(__dirname, './src/types'),
'@utils': path.resolve(__dirname, './src/utils')
}
},
// 开发服务器配置
server: {
port: 3000,
open: true, // 自动打开浏览器
proxy: {
// API代理(开发环境)
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
},
// 构建配置
build: {
outDir: 'dist',
sourcemap: false, // 生产环境不生成sourcemap
rollupOptions: {
output: {
// 代码分割
manualChunks: {
'react-vendor': ['react', 'react-dom', 'react-router-dom'],
'ui-vendor': ['@headlessui/react', 'lucide-react'],
'data-vendor': ['axios', '@tanstack/react-query', 'zustand']
}
}
},
// 压缩配置
minify: 'terser',
terserOptions: {
compress: {
drop_console: true, // 移除console
drop_debugger: true
}
}
},
// 优化依赖预构建
optimizeDeps: {
include: ['react', 'react-dom', 'react-router-dom']
}
});
📘 三、TypeScript配置优化
3.1 严格模式配置
// tsconfig.json
{
"compilerOptions": {
// 基础配置
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
// 模块解析
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
// 严格类型检查
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
// 路径映射(与vite.config.ts保持一致)
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@api/*": ["./src/api/*"],
"@hooks/*": ["./src/hooks/*"],
"@store/*": ["./src/store/*"],
"@types/*": ["./src/types/*"],
"@utils/*": ["./src/utils/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
3.2 类型定义示例
// src/types/workflow.ts
/**
* 工作流相关类型定义
*/
export interface Workflow {
id: string;
name: string;
description: string;
nodes: WorkflowNode[];
edges: WorkflowEdge[];
created_at: string;
updated_at: string;
owner_id: string;
is_active: boolean;
}
export interface WorkflowNode {
id: string;
type: NodeType;
position: Position;
data: NodeData;
}
export type NodeType =
| 'trigger'
| 'action'
| 'condition'
| 'loop'
| 'transform';
export interface Position {
x: number;
y: number;
}
export interface NodeData {
label: string;
config: Record<string, unknown>;
connector?: string;
}
export interface WorkflowEdge {
id: string;
source: string;
target: string;
type?: 'default' | 'conditional';
label?: string;
}
// API响应类型
export interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
error?: string;
}
export interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
page_size: number;
total_pages: number;
}
// 表单类型
export interface CreateWorkflowInput {
name: string;
description?: string;
}
export interface UpdateWorkflowInput extends Partial<CreateWorkflowInput> {
is_active?: boolean;
}
🎨 四、TailwindCSS配置与主题定制
4.1 安装与配置
# 安装TailwindCSS及依赖
npm install -D tailwindcss postcss autoprefixer
# 生成配置文件
npx tailwindcss init -p
4.2 自定义主题配置
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
// 自定义颜色
colors: {
primary: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6', // 主色
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
950: '#172554',
},
success: {
light: '#d1fae5',
DEFAULT: '#10b981',
dark: '#065f46',
},
warning: {
light: '#fef3c7',
DEFAULT: '#f59e0b',
dark: '#92400e',
},
error: {
light: '#fee2e2',
DEFAULT: '#ef4444',
dark: '#991b1b',
},
// 工作流编辑器专用颜色
node: {
trigger: '#8b5cf6',
action: '#3b82f6',
condition: '#f59e0b',
loop: '#10b981',
transform: '#ec4899',
}
},
// 自定义字体
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
mono: ['Fira Code', 'monospace'],
},
// 自定义间距
spacing: {
'128': '32rem',
'144': '36rem',
},
// 自定义圆角
borderRadius: {
'4xl': '2rem',
},
// 自定义阴影
boxShadow: {
'inner-lg': 'inset 0 2px 4px 0 rgb(0 0 0 / 0.1)',
'glow': '0 0 20px rgba(59, 130, 246, 0.5)',
},
// 自定义动画
animation: {
'spin-slow': 'spin 3s linear infinite',
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'bounce-slow': 'bounce 2s infinite',
},
// 自定义关键帧
keyframes: {
'slide-in': {
'0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(0)' },
},
'fade-in': {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
}
}
},
},
plugins: [
// 表单插件
require('@tailwindcss/forms'),
// 排版插件
require('@tailwindcss/typography'),
// 自定义插件
function({ addUtilities }) {
const newUtilities = {
'.scrollbar-hide': {
/* IE and Edge */
'-ms-overflow-style': 'none',
/* Firefox */
'scrollbar-width': 'none',
/* Safari and Chrome */
'&::-webkit-scrollbar': {
display: 'none'
}
}
}
addUtilities(newUtilities)
}
],
}
4.3 全局样式
/* src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
/* 自定义基础样式 */
@layer base {
* {
@apply border-border;
}
body {
@apply bg-gray-50 text-gray-900 antialiased;
}
h1, h2, h3, h4, h5, h6 {
@apply font-semibold;
}
h1 {
@apply text-4xl;
}
h2 {
@apply text-3xl;
}
h3 {
@apply text-2xl;
}
}
/* 自定义组件样式 */
@layer components {
.btn {
@apply px-4 py-2 rounded-lg font-medium transition-colors duration-200;
}
.btn-primary {
@apply bg-primary-500 text-white hover:bg-primary-600 active:bg-primary-700;
}
.btn-secondary {
@apply bg-gray-200 text-gray-900 hover:bg-gray-300 active:bg-gray-400;
}
.card {
@apply bg-white rounded-lg shadow-md p-6;
}
.input {
@apply w-full px-4 py-2 border border-gray-300 rounded-lg
focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent;
}
}
/* 自定义工具类 */
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
🛣️ 五、React Router v6路由设计
5.1 路由配置
// src/routes/index.tsx
import { createBrowserRouter, Navigate } from 'react-router-dom';
import { lazy, Suspense } from 'react';
// 布局组件
import Layout from '@components/layout/Layout';
import LoadingSpinner from '@components/common/LoadingSpinner';
// 懒加载页面组件
const Dashboard = lazy(() => import('@/features/dashboard/Dashboard'));
const WorkflowList = lazy(() => import('@/features/workflows/WorkflowList'));
const WorkflowEditor = lazy(() => import('@/features/workflows/WorkflowEditor'));
const WorkflowExecution = lazy(() => import('@/features/workflows/WorkflowExecution'));
const Login = lazy(() => import('@/features/auth/Login'));
const Register = lazy(() => import('@/features/auth/Register'));
const NotFound = lazy(() => import('@/features/common/NotFound'));
// 受保护路由组件
import ProtectedRoute from './ProtectedRoute';
// 路由配置
export const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
children: [
{
index: true,
element: <Navigate to="/dashboard" replace />
},
{
path: 'dashboard',
element: (
<ProtectedRoute>
<Suspense fallback={<LoadingSpinner />}>
<Dashboard />
</Suspense>
</ProtectedRoute>
)
},
{
path: 'workflows',
children: [
{
index: true,
element: (
<ProtectedRoute>
<Suspense fallback={<LoadingSpinner />}>
<WorkflowList />
</Suspense>
</ProtectedRoute>
)
},
{
path: 'new',
element: (
<ProtectedRoute>
<Suspense fallback={<LoadingSpinner />}>
<WorkflowEditor />
</Suspense>
</ProtectedRoute>
)
},
{
path: ':workflowId',
element: (
<ProtectedRoute>
<Suspense fallback={<LoadingSpinner />}>
<WorkflowEditor />
</Suspense>
</ProtectedRoute>
)
},
{
path: ':workflowId/executions/:executionId',
element: (
<ProtectedRoute>
<Suspense fallback={<LoadingSpinner />}>
<WorkflowExecution />
</Suspense>
</ProtectedRoute>
)
}
]
}
]
},
{
path: '/login',
element: (
<Suspense fallback={<LoadingSpinner />}>
<Login />
</Suspense>
)
},
{
path: '/register',
element: (
<Suspense fallback={<LoadingSpinner />}>
<Register />
</Suspense>
)
},
{
path: '*',
element: (
<Suspense fallback={<LoadingSpinner />}>
<NotFound />
</Suspense>
)
}
]);
5.2 受保护路由实现
// src/routes/ProtectedRoute.tsx
import { Navigate, useLocation } from 'react-router-dom';
import { useAuthStore } from '@store/authStore';
interface ProtectedRouteProps {
children: React.ReactNode;
}
export default function ProtectedRoute({ children }: ProtectedRouteProps) {
const { isAuthenticated } = useAuthStore();
const location = useLocation();
if (!isAuthenticated) {
// 重定向到登录页,并保存当前路径
return <Navigate to="/login" state={{ from: location }} replace />;
}
return <>{children}</>;
}
🗄️ 六、Zustand状态管理
6.1 认证状态管理
// src/store/authStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
interface User {
id: string;
username: string;
email: string;
full_name?: string;
}
interface AuthState {
// 状态
user: User | null;
accessToken: string | null;
refreshToken: string | null;
isAuthenticated: boolean;
// 操作
login: (user: User, accessToken: string, refreshToken: string) => void;
logout: () => void;
updateUser: (user: Partial<User>) => void;
setTokens: (accessToken: string, refreshToken: string) => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
// 初始状态
user: null,
accessToken: null,
refreshToken: null,
isAuthenticated: false,
// 登录
login: (user, accessToken, refreshToken) => set({
user,
accessToken,
refreshToken,
isAuthenticated: true
}),
// 登出
logout: () => set({
user: null,
accessToken: null,
refreshToken: null,
isAuthenticated: false
}),
// 更新用户信息
updateUser: (userData) => set((state) => ({
user: state.user ? { ...state.user, ...userData } : null
})),
// 更新Token
setTokens: (accessToken, refreshToken) => set({
accessToken,
refreshToken
})
}),
{
name: 'auth-storage', // localStorage key
storage: createJSONStorage(() => localStorage),
// 只持久化部分字段
partialize: (state) => ({
user: state.user,
accessToken: state.accessToken,
refreshToken: state.refreshToken,
isAuthenticated: state.isAuthenticated
})
}
)
);
6.2 工作流状态管理
// src/store/workflowStore.ts
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
import type { Workflow, WorkflowNode, WorkflowEdge } from '@types/workflow';
interface WorkflowState {
// 当前编辑的工作流
currentWorkflow: Workflow | null;
nodes: WorkflowNode[];
edges: WorkflowEdge[];
// 选中状态
selectedNodeId: string | null;
selectedEdgeId: string | null;
// 操作
setCurrentWorkflow: (workflow: Workflow) => void;
addNode: (node: WorkflowNode) => void;
updateNode: (nodeId: string, data: Partial<WorkflowNode>) => void;
deleteNode: (nodeId: string) => void;
addEdge: (edge: WorkflowEdge) => void;
deleteEdge: (edgeId: string) => void;
selectNode: (nodeId: string | null) => void;
selectEdge: (edgeId: string | null) => void;
clearWorkflow: () => void;
}
export const useWorkflowStore = create<WorkflowState>()(
devtools(
(set) => ({
// 初始状态
currentWorkflow: null,
nodes: [],
edges: [],
selectedNodeId: null,
selectedEdgeId: null,
// 设置当前工作流
setCurrentWorkflow: (workflow) => set({
currentWorkflow: workflow,
nodes: workflow.nodes,
edges: workflow.edges
}),
// 添加节点
addNode: (node) => set((state) => ({
nodes: [...state.nodes, node]
})),
// 更新节点
updateNode: (nodeId, data) => set((state) => ({
nodes: state.nodes.map(node =>
node.id === nodeId ? { ...node, ...data } : node
)
})),
// 删除节点
deleteNode: (nodeId) => set((state) => ({
nodes: state.nodes.filter(node => node.id !== nodeId),
edges: state.edges.filter(edge =>
edge.source !== nodeId && edge.target !== nodeId
),
selectedNodeId: state.selectedNodeId === nodeId ? null : state.selectedNodeId
})),
// 添加边
addEdge: (edge) => set((state) => ({
edges: [...state.edges, edge]
})),
// 删除边
deleteEdge: (edgeId) => set((state) => ({
edges: state.edges.filter(edge => edge.id !== edgeId),
selectedEdgeId: state.selectedEdgeId === edgeId ? null : state.selectedEdgeId
})),
// 选中节点
selectNode: (nodeId) => set({
selectedNodeId: nodeId,
selectedEdgeId: null
}),
// 选中边
selectEdge: (edgeId) => set({
selectedEdgeId: edgeId,
selectedNodeId: null
}),
// 清空工作流
clearWorkflow: () => set({
currentWorkflow: null,
nodes: [],
edges: [],
selectedNodeId: null,
selectedEdgeId: null
})
}),
{ name: 'WorkflowStore' }
)
);
🌐 七、API请求封装
7.1 Axios实例配置
// src/api/client.ts
import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';
import { useAuthStore } from '@store/authStore';
// 创建Axios实例
export const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api',
timeout: 30000,
headers: {
'Content-Type': 'application/json'
}
});
// 请求拦截器
apiClient.interceptors.request.use(
(config: InternalAxiosRequestConfig) => {
// 添加认证Token
const { accessToken } = useAuthStore.getState();
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
},
(error: AxiosError) => {
return Promise.reject(error);
}
);
// 响应拦截器
apiClient.interceptors.response.use(
(response) => {
return response.data;
},
async (error: AxiosError) => {
const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };
// Token过期,尝试刷新
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const { refreshToken, setTokens } = useAuthStore.getState();
if (!refreshToken) {
throw new Error('No refresh token');
}
// 刷新Token
const response = await axios.post(
`${import.meta.env.VITE_API_BASE_URL}/auth/refresh`,
{ refresh_token: refreshToken }
);
const { access_token, refresh_token } = response.data;
// 更新Token
setTokens(access_token, refresh_token);
// 重试原请求
originalRequest.headers.Authorization = `Bearer ${access_token}`;
return apiClient(originalRequest);
} catch (refreshError) {
// 刷新失败,登出用户
useAuthStore.getState().logout();
window.location.href = '/login';
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
7.2 React Query集成
// src/api/workflows.ts
import { apiClient } from './client';
import type { Workflow, CreateWorkflowInput, UpdateWorkflowInput, PaginatedResponse } from '@types/workflow';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// API函数
export const workflowApi = {
// 获取工作流列表
getWorkflows: async (page = 1, pageSize = 20) => {
return apiClient.get<PaginatedResponse<Workflow>>('/workflows', {
params: { page, page_size: pageSize }
});
},
// 获取单个工作流
getWorkflow: async (workflowId: string) => {
return apiClient.get<Workflow>(`/workflows/${workflowId}`);
},
// 创建工作流
createWorkflow: async (data: CreateWorkflowInput) => {
return apiClient.post<Workflow>('/workflows', data);
},
// 更新工作流
updateWorkflow: async (workflowId: string, data: UpdateWorkflowInput) => {
return apiClient.put<Workflow>(`/workflows/${workflowId}`, data);
},
// 删除工作流
deleteWorkflow: async (workflowId: string) => {
return apiClient.delete(`/workflows/${workflowId}`);
}
};
// React Query Hooks
export function useWorkflows(page = 1, pageSize = 20) {
return useQuery({
queryKey: ['workflows', page, pageSize],
queryFn: () => workflowApi.getWorkflows(page, pageSize),
staleTime: 5 * 60 * 1000, // 5分钟
gcTime: 10 * 60 * 1000 // 10分钟
});
}
export function useWorkflow(workflowId: string) {
return useQuery({
queryKey: ['workflow', workflowId],
queryFn: () => workflowApi.getWorkflow(workflowId),
enabled: !!workflowId
});
}
export function useCreateWorkflow() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: workflowApi.createWorkflow,
onSuccess: () => {
// 创建成功后,使工作流列表缓存失效
queryClient.invalidateQueries({ queryKey: ['workflows'] });
}
});
}
export function useUpdateWorkflow() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ workflowId, data }: { workflowId: string; data: UpdateWorkflowInput }) =>
workflowApi.updateWorkflow(workflowId, data),
onSuccess: (_, variables) => {
// 更新成功后,使相关缓存失效
queryClient.invalidateQueries({ queryKey: ['workflow', variables.workflowId] });
queryClient.invalidateQueries({ queryKey: ['workflows'] });
}
});
}
export function useDeleteWorkflow() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: workflowApi.deleteWorkflow,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['workflows'] });
}
});
}
🎨 八、UI组件示例
8.1 按钮组件
// src/components/ui/Button.tsx
import { ButtonHTMLAttributes, forwardRef } from 'react';
import { cn } from '@utils/cn';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = 'primary', size = 'md', isLoading, children, disabled, ...props }, ref) => {
const baseStyles = 'inline-flex items-center justify-center rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed';
const variants = {
primary: 'bg-primary-500 text-white hover:bg-primary-600 focus:ring-primary-500',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500',
outline: 'border-2 border-primary-500 text-primary-500 hover:bg-primary-50 focus:ring-primary-500',
ghost: 'text-gray-700 hover:bg-gray-100 focus:ring-gray-500',
danger: 'bg-error-500 text-white hover:bg-error-600 focus:ring-error-500'
};
const sizes = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg'
};
return (
<button
ref={ref}
className={cn(baseStyles, variants[variant], sizes[size], className)}
disabled={disabled || isLoading}
{...props}
>
{isLoading && (
<svg className="animate-spin -ml-1 mr-2 h-4 w-4" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
</svg>
)}
{children}
</button>
);
}
);
Button.displayName = 'Button';
export default Button;
// 工具函数
// src/utils/cn.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
💡 九、代码质量工具配置
9.1 ESLint配置
// .eslintrc.cjs
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime'
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'warn',
'react/prop-types': 'off'
},
settings: {
react: {
version: 'detect'
}
}
}
9.2 Prettier配置
// .prettierrc
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"arrowParens": "always",
"endOfLine": "lf"
}
💡 本文小结
核心要点回顾:
-
Vite优势:
- 极速的开发体验(ESM + esbuild)
- 开箱即用的TypeScript支持
- 高效的生产构建(Rollup)
-
TypeScript最佳实践:
- 启用严格模式
- 合理的类型定义
- 路径别名配置
-
TailwindCSS原子化CSS:
- 快速开发UI
- 主题定制
- 响应式设计
-
React Router v6:
- 声明式路由配置
- 懒加载优化
- 受保护路由
-
Zustand状态管理:
- 轻量级(1.2KB)
- 简单易用
- 持久化支持
-
React Query数据管理:
- 自动缓存
- 后台更新
- 乐观更新
性能优化建议:
// 性能优化清单
const PERFORMANCE_CHECKLIST = {
"代码分割": [
"路由级别懒加载",
"组件级别动态导入",
"第三方库按需加载"
],
"资源优化": [
"图片懒加载",
"使用WebP格式",
"CDN加速"
],
"渲染优化": [
"使用React.memo避免不必要的重渲染",
"使用useMemo/useCallback缓存计算结果",
"虚拟滚动处理长列表"
],
"网络优化": [
"API请求去重",
"数据预取",
"离线缓存"
]
};
📦 本文资源
项目脚手架:
# 克隆前端项目模板
git clone https://github.com/quantumflow/frontend-template.git
cd frontend-template
npm install
npm run dev
配置文件:
- ✅
vite.config.ts- Vite配置 - ✅
tsconfig.json- TypeScript配置 - ✅
tailwind.config.js- TailwindCSS配置 - ✅
.eslintrc.cjs- ESLint配置 - ✅
.prettierrc- Prettier配置
VSCode推荐插件:
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss",
"ms-vscode.vscode-typescript-next",
"usernamehw.errorlens",
"eamodio.gitlens"
]
}
🤔 思考题
-
构建工具选择:在什么场景下应该选择Next.js而不是Vite?
-
状态管理:如何决定哪些状态应该放在Zustand,哪些应该用React Query?
-
性能优化:如何监控和优化前端应用的性能?
-
类型安全:如何确保前后端API类型的一致性?
-
代码分割:如何设计合理的代码分割策略?
欢迎在评论区分享你的思考和经验!
作者:DREAMVFIA
专注于企业级应用架构设计与工作流自动化技术
版权声明:
本文为QuantumFlow专栏原创内容,遵循MIT开源协议。欢迎转载,请注明出处。
更多推荐



所有评论(0)