React开发最佳实践 - 从入门到精通

🚀 React开发最佳实践

从基础到高级的系统化实战指南

📚 目录

🎯 第一部分:基础原则与组件设计

React核心概念

React的核心在于组件化思维和单向数据流。理解这些基础概念是编写高质量React代码的前提。

关键原则:
  • 组件应该只负责一个功能
  • 数据应该单向流动
  • 保持组件的纯函数性

组件设计最佳实践

✅ 推荐:函数组件

// 推荐:使用函数组件 + Hooks
const UserCard = ({ user, onEdit }) => {
  const [isEditing, setIsEditing] = useState(false);
  
  const handleEdit = () => {
    setIsEditing(!isEditing);
    onEdit(user.id);
  };
  
  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      <button onClick={handleEdit}>
        {isEditing ? '取消' : '编辑'}
      </button>
    </div>
  );
};

❌ 避免:类组件(除非必须)

// 避免:除非需要使用生命周期方法
class UserCard extends React.Component {
  state = { isEditing: false };
  
  handleEdit = () => {
    this.setState(prevState => ({
      isEditing: !prevState.isEditing
    }));
  };
  
  render() {
    const { user } = this.props;
    const { isEditing } = this.state;
    
    return (
      <div className="user-card">
        <h3>{user.name}</h3>
        <button onClick={this.handleEdit}>
          {isEditing ? '取消' : '编辑'}
        </button>
      </div>
    );
  }
}

Props设计规范

// 好的Props设计
interface ButtonProps {
  variant: 'primary' | 'secondary' | 'danger';
  size: 'small' | 'medium' | 'large';
  disabled?: boolean;
  loading?: boolean;
  onClick?: (event: React.MouseEvent) => void;
  children: React.ReactNode;
  className?: string;
}

// 组件实现
const Button: React.FC<ButtonProps> = ({
  variant = 'primary',
  size = 'medium',
  disabled = false,
  loading = false,
  onClick,
  children,
  className = '',
  ...rest
}) => {
  const baseClasses = 'btn';
  const variantClasses = `btn-${variant}`;
  const sizeClasses = `btn-${size}`;
  const stateClasses = `${disabled ? 'disabled' : ''} ${loading ? 'loading' : ''}`;
  
  return (
    <button
      className={`${baseClasses} ${variantClasses} ${sizeClasses} ${stateClasses} ${className}`}
      disabled={disabled || loading}
      onClick={onClick}
      {...rest}
    >
      {loading ? <Spinner /> : children}
    </button>
  );
};
💡 小贴士:使用TypeScript的联合类型和可选属性来确保Props的类型安全和灵活性。

⚡ 第二部分:性能优化

避免不必要的重渲染

❌ 反模式:每次都重新创建函数

// 反模式:在render中创建新函数
const UserList = ({ users }) => {
  return (
    <div>
      {users.map(user => (
        <UserCard 
          key={user.id}
          user={user}
          onEdit={(id) => console.log('Edit user', id)} // 每次都创建新函数
        />
      ))}
    </div>
  );
};

✅ 最佳实践:使用useCallback

// 最佳实践:使用useCallback缓存函数
const UserList = ({ users, onEditUser }) => {
  const handleEdit = useCallback((id) => {
    onEditUser(id);
  }, [onEditUser]);
  
  return (
    <div>
      {users.map(user => (
        <UserCard 
          key={user.id}
          user={user}
          onEdit={handleEdit}
        />
      ))}
    </div>
  );
};

// 或者使用内联函数(如果依赖简单)
const UserList = ({ users }) => {
  return (
    <div>
      {users.map(user => (
        <UserCard 
          key={user.id}
          user={user}
          onEdit={() => console.log('Edit user', user.id)}
        />
      ))}
    </div>
  );
};

React.memo使用指南

// 使用React.memo优化组件重渲染
const UserCard = React.memo(({ user, onEdit, onDelete }) => {
  console.log('UserCard rendered:', user.name);
  
  return (
    <div className="user-card">
      <h3>{user.name}</h3>
      <p>{user.email}</p>
      <div className="actions">
        <button onClick={() => onEdit(user.id)}>编辑</button>
        <button onClick={() => onDelete(user.id)}>删除</button>
      </div>
    </div>
  );
}, (prevProps, nextProps) => {
  // 自定义比较函数
  return (
    prevProps.user.id === nextProps.user.id &&
    prevProps.user.name === nextProps.user.name &&
    prevProps.user.email === nextProps.user.email &&
    prevProps.onEdit === nextProps.onEdit &&
    prevProps.onDelete === nextProps.onDelete
  );
});

代码分割与懒加载

// 路由级别的代码分割
import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./pages/Dashboard'));
const UserManagement = lazy(() => import('./pages/UserManagement'));
const Settings = lazy(() => import('./pages/Settings'));

const App = () => {
  return (
    <Router>
      <Suspense fallback={<LoadingSpinner />}>
        <Routes>
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/users" element={<UserManagement />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </Router>
  );
};

// 组件级别的懒加载
const LazyImage = lazy(() => import('./LazyImage'));

const ImageGallery = ({ images }) => {
  return (
    <div className="gallery">
      {images.map(image => (
        <Suspense key={image.id} fallback={<ImagePlaceholder />}>
          <LazyImage src={image.url} alt={image.alt} />
        </Suspense>
      ))}
    </div>
  );
};

性能监控与分析

// 使用React DevTools Profiler分析性能
import { Profiler } from 'react';

const onRenderCallback = (id, phase, actualDuration) => {
  console.log('组件:', id);
  console.log('阶段:', phase);
  console.log('渲染时间:', actualDuration + 'ms');
  
  if (actualDuration > 10) {
    console.warn(`组件 ${id} 渲染时间过长:`, actualDuration + 'ms');
  }
};

const App = () => {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MainContent />
    </Profiler>
  );
};

🪝 第三部分:Hook最佳实践

自定义Hook设计原则

✅ 示例:用户状态管理Hook

// 自定义Hook:用户数据管理
const useUser = (userId) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      try {
        setLoading(true);
        setError(null);
        const userData = await api.getUser(userId);
        setUser(userData);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    if (userId) {
      fetchUser();
    }
  }, [userId]);

  const updateUser = useCallback(async (updates) => {
    try {
      const updatedUser = await api.updateUser(userId, updates);
      setUser(updatedUser);
      return updatedUser;
    } catch (err) {
      setError(err.message);
      throw err;
    }
  }, [userId]);

  const deleteUser = useCallback(async () => {
    try {
      await api.deleteUser(userId);
      setUser(null);
    } catch (err) {
      setError(err.message);
      throw err;
    }
  }, [userId]);

  return {
    user,
    loading,
    error,
    updateUser,
    deleteUser,
    refetch: () => setUser(prev => prev ? {...prev} : null)
  };
};

// 使用自定义Hook
const UserProfile = ({ userId }) => {
  const { user, loading, error, updateUser } = useUser(userId);

  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage message={error} />;
  if (!user) return <NotFound />;

  return (
    <div className="user-profile">
      <h2>{user.name}</h2>
      <UserForm user={user} onSubmit={updateUser} />
    </div>
  );
};

常见Hook陷阱与解决方案

❌ 陷阱:无限循环

// 反模式:无限循环
const DataComponent = ({ userId }) => {
  const [data, setData] = useState([]);
  
  // 错误:每次渲染都重新创建依赖数组
  useEffect(() => {
    fetchData(userId).then(setData);
  }, [userId, fetchData]); // fetchData在useCallback中没有正确依赖
  
  return <DataList data={data} />;
};

// 错误定义fetchData
const fetchData = (id) => {
  return api.get(`/data/${id}`); // 每次都创建新函数
};

✅ 解决方案:正确的依赖管理

// 最佳实践:正确管理依赖
const DataComponent = ({ userId }) => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);

  const fetchData = useCallback(async (id) => {
    try {
      setLoading(true);
      const result = await api.get(`/data/${id}`);
      setData(result);
    } finally {
      setLoading(false);
    }
  }, []); // 空依赖数组,因为函数内部没有使用外部变量

  useEffect(() => {
    if (userId) {
      fetchData(userId);
    }
  }, [userId, fetchData]); // 正确依赖

  // 或者使用useRef缓存函数
  const fetchDataRef = useRef(fetchData);
  fetchDataRef.current = fetchData;

  useEffect(() => {
    if (userId) {
      fetchDataRef.current(userId);
    }
  }, [userId]); // 只需要userId依赖

  return (
    <div>
      {loading ? <LoadingSpinner /> : <DataList data={data} />}
    </div>
  );
};

Hook命名规范

// ✅ 正确:使用"use"前缀
const useLocalStorage = (key, initialValue) => {
  // hook实现
};

const useApi = (url) => {
  // hook实现
};

const useDebounce = (value, delay) => {
  // hook实现
};

const useWindowSize = () => {
  // hook实现
};

// ❌ 错误:不使用"use"前缀
const localStorage = (key, initialValue) => {
  // 这不是hook!
};

🔄 第四部分:状态管理进阶

何时使用Context

场景 推荐方案 原因
全局用户认证状态 Context + useReducer 需要全局访问,更新逻辑复杂
主题设置 Context 全局状态,更新简单
表单状态 useState/useReducer 组件局部状态
复杂列表状态 useReducer + useMemo 状态逻辑复杂但作用域有限
服务器状态 SWR/React Query 专门的缓存和同步机制

Context性能优化

// 优化:拆分Context
const UserContext = React.createContext();
const ThemeContext = React.createContext();

// 用户状态Provider
const UserProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  
  const value = useMemo(() => ({
    user,
    setUser,
    login: (userData) => setUser(userData),
    logout: () => setUser(null)
  }), [user]);
  
  return (
    <UserContext.Provider value={value}>
      {children}
    </UserContext.Provider>
  );
};

// 主题状态Provider
const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');
  
  const value = useMemo(() => ({
    theme,
    toggleTheme: () => setTheme(prev => prev === 'light' ? 'dark' : 'light')
  }), []);
  
  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
};

// 使用优化后的Context
const Header = () => {
  const { user, logout } = useContext(UserContext);
  const { theme, toggleTheme } = useContext(ThemeContext);
  
  // 只有当用户或主题变化时才重新渲染
  return (
    <header className={theme}>
      {user && <WelcomeMessage user={user} />}
      <button onClick={toggleTheme}>切换主题</button>
      {user && <button onClick={logout}>退出</button>}
    </header>
  );
};

状态管理库选择指南

// Redux Toolkit - 适用于大型复杂应用
import { createSlice } from '@reduxjs/toolkit';

const userSlice = createSlice({
  name: 'user',
  initialState: {
    currentUser: null,
    users: [],
    loading: false
  },
  reducers: {
    setUser: (state, action) => {
      state.currentUser = action.payload;
    },
    setUsers: (state, action) => {
      state.users = action.payload;
    },
    setLoading: (state, action) => {
      state.loading = action.payload;
    }
  }
});

// Zustand - 适用于中小型项目
import create from 'zustand';

const useStore = create((set, get) => ({
  user: null,
  setUser: (user) => set({ user }),
  users: [],
  setUsers: (users) => set({ users }),
  fetchUser: async (id) => {
    const user = await api.getUser(id);
    set({ user });
  }
}));

// Jotai - 适用于原子化状态管理
import { atom, useAtom } from 'jotai';

const userAtom = atom(null);
const loadingAtom = atom(false);

const UserComponent = () => {
  const [user, setUser] = useAtom(userAtom);
  const [loading, setLoading] = useAtom(loadingAtom);
  
  // 使用逻辑
};

🏗️ 第五部分:代码组织与架构

项目结构规范

推荐的项目结构:
  • 特性化目录结构 - 按功能模块组织
  • 组件复用性 - 通用组件与业务组件分离
  • 工具函数集中管理 - 避免重复代码
src/
├── components/           # 通用组件
│   ├── ui/              # UI基础组件
│   │   ├── Button/
│   │   ├── Modal/
│   │   └── Input/
│   ├── layout/          # 布局组件
│   │   ├── Header/
│   │   ├── Footer/
│   │   └── Sidebar/
│   └── common/          # 业务通用组件
│       ├── UserAvatar/
│       ├── DataTable/
│       └── SearchBox/
├── features/            # 功能模块
│   ├── auth/           # 认证模块
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── utils/
│   │   ├── types.ts
│   │   └── index.ts
│   ├── dashboard/      # 仪表板模块
│   └── users/          # 用户管理模块
├── hooks/              # 全局自定义Hooks
├── utils/              # 工具函数
├── types/              # TypeScript类型定义
├── constants/          # 常量定义
└── styles/             # 样式文件

组件组织方式

// components/ui/Button/index.tsx
import React from 'react';
import './Button.css';

export interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
  loading?: boolean;
  children: React.ReactNode;
  onClick?: (e: React.MouseEvent) => void;
  className?: string;
  type?: 'button' | 'submit' | 'reset';
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ 
    variant = 'primary', 
    size = 'medium', 
    disabled = false, 
    loading = false,
    children, 
    onClick, 
    className = '',
    type = 'button',
    ...props 
  }, ref) => {
    const handleClick = (e: React.MouseEvent) => {
      if (!disabled && !loading && onClick) {
        onClick(e);
      }
    };

    return (
      <button
        ref={ref}
        type={type}
        className={`btn btn--${variant} btn--${size} ${className}`}
        disabled={disabled || loading}
        onClick={handleClick}
        {...props}
      >
        {loading && <span className="btn__spinner" />}
        <span className="btn__content">{children}</span>
      </button>
    );
  }
);

Button.displayName = 'Button';

export default Button;

// features/users/components/UserList/UserList.tsx
import React, { memo } from 'react';
import { User } from '../../types';
import UserCard from '../UserCard';
import { useUsers } from '../../hooks/useUsers';

interface UserListProps {
  searchTerm?: string;
  onUserSelect?: (user: User) => void;
}

const UserList: React.FC<UserListProps> = memo(({ searchTerm = '', onUserSelect }) => {
  const { users, loading, error } = useUsers();
  
  const filteredUsers = useMemo(() => {
    if (!searchTerm) return users;
    return users.filter(user => 
      user.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
      user.email.toLowerCase().includes(searchTerm.toLowerCase())
    );
  }, [users, searchTerm]);

  if (loading) return <UserListSkeleton />;
  if (error) return <ErrorMessage message={error} />;

  return (
    <div className="user-list">
      {filteredUsers.map(user => (
        <UserCard
          key={user.id}
          user={user}
          onClick={() => onUserSelect?.(user)}
        />
      ))}
    </div>
  );
});

UserList.displayName = 'UserList';

export default UserList;

TypeScript集成最佳实践

// types/common.ts - 通用类型定义
export interface BaseEntity {
  id: string;
  createdAt: Date;
  updatedAt: Date;
}

export interface User extends BaseEntity {
  name: string;
  email: string;
  avatar?: string;
  role: 'admin' | 'user' | 'moderator';
  isActive: boolean;
}

export interface ApiResponse<T> {
  data: T;
  message?: string;
  success: boolean;
  error?: string;
}

// types/features/users.ts - 功能相关类型
export interface UserFilters {
  search?: string;
  role?: User['role'];
  isActive?: boolean;
  sortBy?: 'name' | 'email' | 'createdAt';
  sortOrder?: 'asc' | 'desc';
}

export interface UserFormData {
  name: string;
  email: string;
  role: User['role'];
}

// 泛型组件示例
interface TableProps<T> {
  data: T[];
  columns: ColumnDef<T>[];
  onRowClick?: (row: T) => void;
  loading?: boolean;
}

interface ColumnDef<T> {
  key: keyof T;
  title: string;
  render?: (value: T[keyof T], row: T) => React.ReactNode;
  sortable?: boolean;
}

const Table = <T extends Record<string, any>>({
  data,
  columns,
  onRowClick,
  loading = false
}: TableProps<T>) => {
  // 组件实现
};

🧪 第六部分:测试策略

单元测试最佳实践

✅ 使用React Testing Library

// components/Button/Button.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Button from './Button';

describe('Button', () => {
  it('应该正确渲染按钮文本', () => {
    render(<Button>点击我</Button>);
    
    expect(screen.getByRole('button', { name: '点击我' })).toBeInTheDocument();
  });

  it('应该在点击时调用onClick回调', async () => {
    const handleClick = jest.fn();
    const user = userEvent.setup();
    
    render(<Button onClick={handleClick}>点击我</Button>);
    
    await user.click(screen.getByRole('button'));
    
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('应该在loading时显示加载状态并禁用按钮', () => {
    render(<Button loading>加载中</Button>);
    
    const button = screen.getByRole('button');
    expect(button).toBeDisabled();
    expect(screen.getByText('加载中')).toBeInTheDocument();
  });

  it('应该支持不同的变体样式', () => {
    const { rerender } = render(<Button variant="primary">主要按钮</Button>);
    
    let button = screen.getByRole('button');
    expect(button).toHaveClass('btn--primary');
    
    rerender(<Button variant="secondary">次要按钮</Button>);
    button = screen.getByRole('button');
    expect(button).toHaveClass('btn--secondary');
  });
});

组件集成测试

// features/users/components/UserList/UserList.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import UserList from './UserList';
import { useUsers } from '../../hooks/useUsers';

// Mock自定义Hook
jest.mock('../../hooks/useUsers');
const mockUseUsers = useUsers as jest.MockedFunction<typeof useUsers>;

describe('UserList', () => {
  const mockUsers = [
    { id: '1', name: '张三', email: '[email protected]', role: 'user', isActive: true },
    { id: '2', name: '李四', email: '[email protected]', role: 'admin', isActive: false }
  ];

  beforeEach(() => {
    mockUseUsers.mockReturnValue({
      users: mockUsers,
      loading: false,
      error: null,
      refetch: jest.fn()
    });
  });

  it('应该渲染用户列表', async () => {
    render(<UserList />);
    
    await waitFor(() => {
      expect(screen.getByText('张三')).toBeInTheDocument();
      expect(screen.getByText('李四')).toBeInTheDocument();
    });
  });

  it('应该根据搜索词过滤用户', async () => {
    const user = userEvent.setup();
    
    render(<UserList />);
    
    const searchInput = screen.getByPlaceholderText('搜索用户...');
    await user.type(searchInput, '张三');
    
    await waitFor(() => {
      expect(screen.getByText('张三')).toBeInTheDocument();
      expect(screen.queryByText('李四')).not.toBeInTheDocument();
    });
  });

  it('应该在用户为空时显示空状态', () => {
    mockUseUsers.mockReturnValue({
      users: [],
      loading: false,
      error: null,
      refetch: jest.fn()
    });
    
    render(<UserList />);
    
    expect(screen.getByText('暂无用户数据')).toBeInTheDocument();
  });
});

异步操作测试

// hooks/useUser/useUser.test.ts
import { renderHook, waitFor } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { useUser } from './useUser';
import * as api from '../../api/users';

// Mock API
jest.mock('../../api/users');
const mockApi = api as jest.Mocked<typeof api>;

describe('useUser', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('应该初始加载用户数据', async () => {
    const mockUser = { id: '1', name: '张三', email: '[email protected]' };
    mockApi.getUser.mockResolvedValue(mockUser);

    const { result } = renderHook(() => useUser('1'));

    expect(result.current.loading).toBe(true);
    
    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });
    
    expect(result.current.user).toEqual(mockUser);
    expect(result.current.error).toBeNull();
  });

  it('应该在API调用失败时设置错误状态', async () => {
    const mockError = new Error('用户不存在');
    mockApi.getUser.mockRejectedValue(mockError);

    const { result } = renderHook(() => useUser('999'));

    expect(result.current.loading).toBe(true);
    
    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });
    
    expect(result.current.user).toBeNull();
    expect(result.current.error).toBe('用户不存在');
  });

  it('应该正确更新用户信息', async () => {
    const initialUser = { id: '1', name: '张三', email: '[email protected]' };
    const updatedUser = { id: '1', name: '张三丰', email: '[email protected]' };
    
    mockApi.getUser.mockResolvedValue(initialUser);
    mockApi.updateUser.mockResolvedValue(updatedUser);

    const { result } = renderHook(() => useUser('1'));
    
    // 等待初始加载完成
    await waitFor(() => {
      expect(result.current.loading).toBe(false);
    });

    // 执行更新操作
    await act(async () => {
      await result.current.updateUser({ name: '张三丰' });
    });

    expect(mockApi.updateUser).toHaveBeenCalledWith('1', { name: '张三丰' });
    expect(result.current.user).toEqual(updatedUser);
  });
});

🛠️ 第七部分:工具链与开发体验

构建工具优化

💡 Vite vs Create React App:
  • Vite - 更快的开发服务器,现代构建工具
  • CRA - 成熟稳定,社区支持丰富
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@': resolve(__dirname, 'src'),
      '@/components': resolve(__dirname, 'src/components'),
      '@/features': resolve(__dirname, 'src/features'),
      '@/hooks': resolve(__dirname, 'src/hooks'),
      '@/utils': resolve(__dirname, 'src/utils')
    }
  },
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
          ui: ['@mui/material', '@emotion/react'],
          utils: ['lodash', 'date-fns']
        }
      }
    },
    chunkSizeWarningLimit: 1000,
    sourcemap: true
  },
  server: {
    port: 3000,
    open: true,
    host: true
  },
  preview: {
    port: 4173
  }
});

代码质量工具配置

// .eslintrc.json
{
  "extends": [
    "react-app",
    "react-app/jest",
    "@typescript-eslint/recommended",
    "prettier"
  ],
  "rules": {
    "react-hooks/exhaustive-deps": "warn",
    "@typescript-eslint/no-unused-vars": "error",
    "prefer-const": "error",
    "no-var": "error",
    "react-hooks/rules-of-hooks": "error"
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  }
}
// package.json scripts
{
  "scripts": {
    "start": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview",
    "lint": "eslint src --ext .ts,.tsx --report-unused-disable-directives --max-warnings 0",
    "lint:fix": "eslint src --ext .ts,.tsx --fix",
    "type-check": "tsc --noEmit",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "prepare": "husky install",
    "format": "prettier --write \"src/**/*.{ts,tsx,json,css,md}\"",
    "check": "npm run type-check && npm run lint && npm run test"
  }
}

Git Hooks集成

#!/bin/sh
# .husky/pre-commit

. "$(dirname "$0")/_/husky.sh"

# 运行类型检查
npm run type-check

# 运行 lint 检查
npm run lint

# 如果检查失败,则阻止提交
if [ $? -ne 0 ]; then
  echo "❌ 提交被阻止:代码质量检查失败"
  exit 1
fi

echo "✅ 代码质量检查通过,允许提交"
// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
  moduleNameMapping: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy'
  },
  collectCoverageFrom: [
    'src/**/*.{ts,tsx}',
    '!src/**/*.d.ts',
    '!src/**/*.stories.{ts,tsx}',
    '!src/index.tsx'
  ],
  coverageThreshold: {
    global: {
      branches: 80,
      functions: 80,
      lines: 80,
      statements: 80
    }
  },
  moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
  transform: {
    '^.+\\.(ts|tsx)$': 'ts-jest'
  }
};

📋 第八部分:实战案例分析

常见反模式与重构

❌ 反模式1:过度使用useEffect

// 反模式:useEffect滥用
const UserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(false);

  // 太多useEffect,难以追踪依赖
  useEffect(() => {
    if (userId) {
      api.getUser(userId).then(setUser);
    }
  }, [userId]);

  useEffect(() => {
    if (user) {
      api.getUserPosts(user.id).then(setPosts);
    }
  }, [user]);

  useEffect(() => {
    if (user && posts.length > 0) {
      localStorage.setItem('user-posts', JSON.stringify(posts));
    }
  }, [user, posts]);

  useEffect(() => {
    document.title = user ? `${user.name}的资料` : '用户资料';
  }, [user]);

  return (
    <div>
      {loading ? <Loading /> : (
        <div>
          <h1>{user?.name}</h1>
          <PostsList posts={posts} />
        </div>
      )}
    </div>
  );
};

✅ 重构后:使用自定义Hook分离关注点

// 自定义Hook:用户数据管理
const useUserProfile = (userId) => {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(false);

  // 使用useCallback缓存函数
  const fetchUser = useCallback(async (id) => {
    setLoading(true);
    try {
      const userData = await api.getUser(id);
      setUser(userData);
      return userData;
    } finally {
      setLoading(false);
    }
  }, []);

  const fetchUserPosts = useCallback(async (userId) => {
    const postsData = await api.getUserPosts(userId);
    setPosts(postsData);
    return postsData;
  }, []);

  // 链式调用,减少嵌套
  useEffect(() => {
    if (userId) {
      fetchUser(userId).then(user => {
        if (user) {
          fetchUserPosts(user.id);
        }
      });
    }
  }, [userId, fetchUser, fetchUserPosts]);

  // 副作用分离到独立的effect
  useEffect(() => {
    if (posts.length > 0) {
      localStorage.setItem('user-posts', JSON.stringify(posts));
    }
  }, [posts]);

  return {
    user,
    posts,
    loading,
    refetch: () => {
      if (userId) {
        fetchUser(userId).then(user => {
          if (user) {
            fetchUserPosts(user.id);
          }
        });
      }
    }
  };
};

// 自定义Hook:页面标题管理
const useDocumentTitle = (title) => {
  useEffect(() => {
    const originalTitle = document.title;
    document.title = title || originalTitle;
    
    return () => {
      document.title = originalTitle;
    };
  }, [title]);
};

// 重构后的组件:职责分离
const UserProfile = ({ userId }) => {
  const { user, posts, loading } = useUserProfile(userId);
  useDocumentTitle(user ? `${user.name}的资料` : '用户资料');

  if (loading) return <Loading />;
  if (!user) return <NotFound />;

  return (
    <div>
      <h1>{user.name}</h1>
      <UserInfo user={user} />
      <PostsList posts={posts} />
    </div>
  );
};

性能问题诊断与解决

问题:列表渲染慢

症状:1000+条数据的列表滚动卡顿

原因:所有列表项同时渲染

解决:虚拟化列表

// 使用react-window
import { FixedSizeList as List } from 'react-window';

const VirtualizedUserList = ({ users }) => {
  const Row = ({ index, style }) => (
    <div style={style}>
      <UserCard user={users[index]} />
    </div>
  );

  return (
    <List
      height={600}          // 容器高度
      itemCount={users.length}
      itemSize={80}         // 每项高度
      width="100%"
    >
      {Row}
    </List>
  );
};

代码重构实战案例

重构前后对比:一个复杂表单组件的重构过程
// 重构前:300行的巨无霸组件
const ComplexForm = () => {
  const [formData, setFormData] = useState({});
  const [errors, setErrors] = useState({});
  const [loading, setLoading] = useState(false);
  const [validationRules, setValidationRules] = useState({});
  const [fieldConfigs, setFieldConfigs] = useState({});
  const [userPermissions, setUserPermissions] = useState({});
  const [draftData, setDraftData] = useState({});
  // ... 20+ 个 state

  // 50+ 个函数处理各种逻辑
  const handleFieldChange = () => { /* ... */ };
  const validateField = () => { /* ... */ };
  const validateForm = () => { /* ... */ };
  const submitForm = async () => { /* ... */ };
  const saveDraft = () => { /* ... */ };
  // ... 更多函数

  // 200+ 行 render 逻辑
  return (
    <form>
      {/* 表单字段 */}
      {/* 验证错误显示 */}
      {/* 权限控制 */}
      {/* 自动保存 */}
      {/* 等等... */}
    </form>
  );
};
// 重构后:模块化组件结构

// 1. 自定义Hook:表单状态管理
const useForm = (initialData, validationRules) => {
  const [data, setData] = useState(initialData);
  const [errors, setErrors] = useState({});
  const [isDirty, setIsDirty] = useState(false);

  const validateField = useCallback((name, value) => {
    const rule = validationRules[name];
    if (!rule) return null;
    
    return rule(value, data);
  }, [validationRules, data]);

  const setFieldValue = useCallback((name, value) => {
    setData(prev => ({ ...prev, [name]: value }));
    setIsDirty(true);
    
    const error = validateField(name, value);
    setErrors(prev => ({ ...prev, [name]: error }));
  }, [validateField]);

  const validateForm = useCallback(() => {
    const newErrors = {};
    Object.keys(validationRules).forEach(field => {
      const error = validateField(field, data[field]);
      if (error) newErrors[field] = error;
    });
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  }, [data, validationRules, validateField]);

  return {
    data,
    errors,
    isDirty,
    setFieldValue,
    validateForm,
    setErrors: setErrors
  };
};

// 2. 权限控制Hook
const usePermissions = () => {
  const { user } = useAuth();
  const [permissions, setPermissions] = useState({});

  useEffect(() => {
    if (user) {
      fetchUserPermissions(user.role).then(setPermissions);
    }
  }, [user]);

  const canEdit = useCallback((field) => {
    return permissions[field]?.canEdit ?? true;
  }, [permissions]);

  return { canEdit, permissions };
};

// 3. 自动保存Hook
const useAutoSave = (data, interval = 30000) => {
  const [lastSaved, setLastSaved] = useState(null);
  
  useEffect(() => {
    const timer = setInterval(() => {
      if (Object.keys(data).length > 0) {
        localStorage.setItem('form-draft', JSON.stringify(data));
        setLastSaved(new Date());
      }
    }, interval);

    return () => clearInterval(timer);
  }, [data, interval]);

  return { lastSaved };
};

// 4. 主组件:职责清晰
const ComplexForm = () => {
  const { user } = useAuth();
  const { canEdit } = usePermissions();
  const { data, errors, setFieldValue, validateForm } = useForm(
    initialFormData,
    validationRules
  );
  const { lastSaved } = useAutoSave(data);

  const handleSubmit = async (formData) => {
    if (validateForm()) {
      await submitForm(formData);
    }
  };

  return (
    <Form onSubmit={handleSubmit}>
      <FormFields 
        data={data}
        errors={errors}
        onFieldChange={setFieldValue}
        canEdit={canEdit}
      />
      <FormActions />
      <AutoSaveIndicator lastSaved={lastSaved} />
    </Form>
  );
};
重构收益:
  • 代码行数从300+减少到50+
  • 函数职责单一,易于测试
  • 组件可复用性大幅提升
  • 性能问题易于定位

最佳实践检查清单

✅ 代码质量检查清单

  • 组件设计 组件只负责一个功能,遵循单一职责原则
  • 性能优化 使用React.memo、useMemo、useCallback优化重渲染
  • 状态管理 合理选择local state、Context或状态管理库
  • 类型安全 使用TypeScript确保类型安全
  • 测试覆盖 关键逻辑和组件有相应的测试
  • 代码规范 使用ESLint和Prettier保持代码一致性
  • 文档注释 复杂逻辑有清晰的注释和文档
  • 错误处理 优雅处理错误和边界情况

互动区域

登录后可以点赞此内容

参与互动

登录后可以点赞和评论此内容,与作者互动交流