Skip to content

代码组织规范 - React项目结构最佳实践

1. 项目结构基础

1.1 标准项目结构

my-react-app/
├── public/                 # 静态资源
│   ├── index.html
│   ├── favicon.ico
│   └── manifest.json
├── src/                    # 源代码
│   ├── assets/            # 资源文件
│   │   ├── images/
│   │   ├── fonts/
│   │   └── styles/
│   ├── components/        # 通用组件
│   │   ├── Button/
│   │   ├── Input/
│   │   └── Modal/
│   ├── features/          # 功能模块
│   │   ├── auth/
│   │   ├── dashboard/
│   │   └── profile/
│   ├── hooks/             # 自定义Hooks
│   ├── services/          # API服务
│   ├── store/             # 状态管理
│   ├── utils/             # 工具函数
│   ├── types/             # TypeScript类型
│   ├── constants/         # 常量
│   ├── config/            # 配置
│   ├── App.tsx
│   ├── index.tsx
│   └── routes.tsx
├── tests/                 # 测试文件
│   ├── unit/
│   ├── integration/
│   └── e2e/
├── .env                   # 环境变量
├── .eslintrc.js          # ESLint配置
├── .prettierrc           # Prettier配置
├── tsconfig.json         # TypeScript配置
├── package.json
└── README.md

1.2 组件组织

components/
├── Button/
│   ├── Button.tsx              # 组件实现
│   ├── Button.test.tsx         # 测试文件
│   ├── Button.stories.tsx      # Storybook故事
│   ├── Button.module.css       # 样式(如果使用CSS Modules)
│   ├── Button.types.ts         # 类型定义
│   ├── index.ts                # 导出
│   └── README.md               # 文档
├── Input/
│   ├── Input.tsx
│   ├── Input.test.tsx
│   ├── Input.types.ts
│   └── index.ts
└── Modal/
    ├── Modal.tsx
    ├── ModalHeader.tsx         # 子组件
    ├── ModalBody.tsx
    ├── ModalFooter.tsx
    ├── Modal.test.tsx
    ├── Modal.types.ts
    └── index.ts

1.3 功能模块组织

features/
├── auth/
│   ├── components/            # 功能专属组件
│   │   ├── LoginForm/
│   │   └── RegisterForm/
│   ├── hooks/                 # 功能专属Hooks
│   │   ├── useAuth.ts
│   │   └── useLogin.ts
│   ├── api/                   # 功能专属API
│   │   └── authApi.ts
│   ├── store/                 # 功能专属状态
│   │   └── authSlice.ts
│   ├── types/                 # 功能专属类型
│   │   └── auth.types.ts
│   ├── utils/                 # 功能专属工具
│   │   └── tokenUtils.ts
│   └── index.ts               # 导出
├── dashboard/
│   ├── components/
│   ├── hooks/
│   ├── api/
│   └── index.ts
└── profile/
    ├── components/
    ├── hooks/
    ├── api/
    └── index.ts

2. 代码组织原则

2.1 单一职责原则

tsx
// ❌ 不好: 组件职责过多
function UserProfile() {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);
  const [followers, setFollowers] = useState([]);
  
  useEffect(() => {
    fetchUser().then(setUser);
    fetchPosts().then(setPosts);
    fetchFollowers().then(setFollowers);
  }, []);
  
  const handleFollow = () => { /* ... */ };
  const handleUnfollow = () => { /* ... */ };
  const handleEditProfile = () => { /* ... */ };
  
  return (
    <div>
      <div>{/* 用户信息 */}</div>
      <div>{/* 帖子列表 */}</div>
      <div>{/* 关注者列表 */}</div>
      <div>{/* 操作按钮 */}</div>
    </div>
  );
}

// ✅ 好: 拆分为多个职责单一的组件
function UserProfile() {
  return (
    <div>
      <UserInfo />
      <UserPosts />
      <UserFollowers />
      <UserActions />
    </div>
  );
}

function UserInfo() {
  const { user, loading, error } = useUser();
  
  if (loading) return <Skeleton />;
  if (error) return <Error error={error} />;
  
  return (
    <div>
      <Avatar src={user.avatar} />
      <h2>{user.name}</h2>
      <p>{user.bio}</p>
    </div>
  );
}

function UserPosts() {
  const { posts, loading } = useUserPosts();
  
  return (
    <div>
      <h3>Posts</h3>
      {loading ? <Skeleton count={3} /> : (
        <PostList posts={posts} />
      )}
    </div>
  );
}

function UserFollowers() {
  const { followers } = useUserFollowers();
  
  return (
    <div>
      <h3>Followers</h3>
      <FollowerList followers={followers} />
    </div>
  );
}

function UserActions() {
  const { isFollowing, handleFollow, handleUnfollow } = useFollowActions();
  const { handleEditProfile } = useProfileActions();
  
  return (
    <div>
      {isFollowing ? (
        <Button onClick={handleUnfollow}>Unfollow</Button>
      ) : (
        <Button onClick={handleFollow}>Follow</Button>
      )}
      <Button onClick={handleEditProfile}>Edit Profile</Button>
    </div>
  );
}

2.2 关注点分离

tsx
// ❌ 不好: 数据获取、业务逻辑、UI混在一起
function ProductList() {
  const [products, setProducts] = useState([]);
  const [filteredProducts, setFilteredProducts] = useState([]);
  const [filter, setFilter] = useState('');
  const [sortBy, setSortBy] = useState('name');
  
  useEffect(() => {
    fetch('/api/products')
      .then(res => res.json())
      .then(data => setProducts(data));
  }, []);
  
  useEffect(() => {
    let filtered = products.filter(p => 
      p.name.toLowerCase().includes(filter.toLowerCase())
    );
    
    filtered = filtered.sort((a, b) => {
      if (sortBy === 'name') return a.name.localeCompare(b.name);
      if (sortBy === 'price') return a.price - b.price;
      return 0;
    });
    
    setFilteredProducts(filtered);
  }, [products, filter, sortBy]);
  
  return (
    <div>
      <input value={filter} onChange={e => setFilter(e.target.value)} />
      <select value={sortBy} onChange={e => setSortBy(e.target.value)}>
        <option value="name">Name</option>
        <option value="price">Price</option>
      </select>
      <ul>
        {filteredProducts.map(product => (
          <li key={product.id}>
            {product.name} - ${product.price}
          </li>
        ))}
      </ul>
    </div>
  );
}

// ✅ 好: 分离数据层、逻辑层、展示层

// 1. 数据层 (hooks/useProducts.ts)
function useProducts() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    setLoading(true);
    fetch('/api/products')
      .then(res => res.json())
      .then(data => {
        setProducts(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, []);
  
  return { products, loading, error };
}

// 2. 逻辑层 (hooks/useProductFilter.ts)
function useProductFilter(products) {
  const [filter, setFilter] = useState('');
  const [sortBy, setSortBy] = useState('name');
  
  const filteredProducts = useMemo(() => {
    let filtered = products.filter(p => 
      p.name.toLowerCase().includes(filter.toLowerCase())
    );
    
    filtered = filtered.sort((a, b) => {
      if (sortBy === 'name') return a.name.localeCompare(b.name);
      if (sortBy === 'price') return a.price - b.price;
      return 0;
    });
    
    return filtered;
  }, [products, filter, sortBy]);
  
  return { filteredProducts, filter, setFilter, sortBy, setSortBy };
}

// 3. 展示层 (components/ProductList.tsx)
function ProductList() {
  const { products, loading, error } = useProducts();
  const { filteredProducts, filter, setFilter, sortBy, setSortBy } = useProductFilter(products);
  
  if (loading) return <Skeleton />;
  if (error) return <Error error={error} />;
  
  return (
    <div>
      <ProductFilters
        filter={filter}
        onFilterChange={setFilter}
        sortBy={sortBy}
        onSortChange={setSortBy}
      />
      <ProductGrid products={filteredProducts} />
    </div>
  );
}

function ProductFilters({ filter, onFilterChange, sortBy, onSortChange }) {
  return (
    <div>
      <Input value={filter} onChange={e => onFilterChange(e.target.value)} placeholder="Search..." />
      <Select value={sortBy} onChange={e => onSortChange(e.target.value)}>
        <option value="name">Name</option>
        <option value="price">Price</option>
      </Select>
    </div>
  );
}

function ProductGrid({ products }) {
  return (
    <ul>
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </ul>
  );
}

function ProductCard({ product }) {
  return (
    <li>
      {product.name} - ${product.price}
    </li>
  );
}

2.3 容器组件与展示组件

tsx
// 展示组件: 只关注UI,通过props接收数据
// components/UserCard.tsx
interface UserCardProps {
  user: User;
  onFollow: (userId: string) => void;
  onUnfollow: (userId: string) => void;
  isFollowing: boolean;
}

function UserCard({ user, onFollow, onUnfollow, isFollowing }: UserCardProps) {
  return (
    <div className="user-card">
      <Avatar src={user.avatar} alt={user.name} />
      <h3>{user.name}</h3>
      <p>{user.bio}</p>
      <Button onClick={() => isFollowing ? onUnfollow(user.id) : onFollow(user.id)}>
        {isFollowing ? 'Unfollow' : 'Follow'}
      </Button>
    </div>
  );
}

// 容器组件: 处理数据和逻辑,将数据传递给展示组件
// containers/UserCardContainer.tsx
function UserCardContainer({ userId }: { userId: string }) {
  const { user, loading, error } = useUser(userId);
  const { isFollowing, follow, unfollow } = useFollowStatus(userId);
  
  if (loading) return <Skeleton />;
  if (error) return <Error error={error} />;
  if (!user) return null;
  
  return (
    <UserCard
      user={user}
      onFollow={follow}
      onUnfollow={unfollow}
      isFollowing={isFollowing}
    />
  );
}

3. 文件命名规范

3.1 命名约定

typescript
const namingConventions = {
  组件文件: {
    React组件: 'PascalCase.tsx',
    示例: ['Button.tsx', 'UserProfile.tsx', 'ProductCard.tsx']
  },
  非组件文件: {
    工具函数: 'camelCase.ts',
    示例: ['formatDate.ts', 'validateEmail.ts', 'apiClient.ts']
  },
  Hooks: {
    格式: 'use开头 + PascalCase.ts',
    示例: ['useAuth.ts', 'useLocalStorage.ts', 'useFetch.ts']
  },
  类型文件: {
    格式: 'name.types.ts',
    示例: ['user.types.ts', 'api.types.ts', 'common.types.ts']
  },
  常量文件: {
    格式: 'SCREAMING_SNAKE_CASE.ts',
    示例: ['API_ENDPOINTS.ts', 'ERROR_MESSAGES.ts', 'ROUTES.ts']
  },
  测试文件: {
    格式: 'name.test.ts''name.spec.ts',
    示例: ['Button.test.tsx', 'formatDate.spec.ts']
  },
  样式文件: {
    CSSModules: 'name.module.css',
    StyledComponents: 'name.styles.ts',
    示例: ['Button.module.css', 'Button.styles.ts']
  }
};

3.2 目录命名

typescript
const directoryNaming = {
  规则: 'kebab-case',
  示例: [
    'user-profile/',
    'product-catalog/',
    'shopping-cart/',
    'error-boundary/'
  ],
  例外: {
    组件目录: 'PascalCase',
    示例: ['Button/', 'Modal/', 'Dropdown/']
  }
};

4. 导入导出规范

4.1 导入顺序

tsx
// ✅ 推荐的导入顺序

// 1. React核心
import React, { useState, useEffect, useMemo } from 'react';

// 2. 第三方库
import { useQuery } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
import classNames from 'classnames';

// 3. 内部模块 (绝对路径)
import { Button } from '@/components/Button';
import { useAuth } from '@/hooks/useAuth';
import { formatDate } from '@/utils/date';

// 4. 相对路径导入
import { UserCard } from './UserCard';
import { useUserData } from './hooks/useUserData';

// 5. 类型导入
import type { User } from '@/types/user';
import type { Props } from './types';

// 6. 样式
import styles from './Component.module.css';
import './Component.css';

3.2 导出规范

tsx
// ❌ 不好: 默认导出 (难以重构和追踪)
export default function Button() {
  return <button>Click</button>;
}

// ✅ 好: 命名导出
export function Button() {
  return <button>Click</button>;
}

// ✅ 使用index.ts统一导出
// components/Button/Button.tsx
export function Button(props: ButtonProps) {
  return <button {...props} />;
}

export type { ButtonProps } from './Button.types';

// components/Button/index.ts
export { Button } from './Button';
export type { ButtonProps } from './Button.types';

// 使用时
import { Button } from '@/components/Button';

4.3 路径别名配置

json
// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "@/*": ["*"],
      "@components/*": ["components/*"],
      "@hooks/*": ["hooks/*"],
      "@utils/*": ["utils/*"],
      "@types/*": ["types/*"],
      "@features/*": ["features/*"],
      "@assets/*": ["assets/*"]
    }
  }
}

// vite.config.ts
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
      '@components': path.resolve(__dirname, './src/components'),
      '@hooks': path.resolve(__dirname, './src/hooks'),
      '@utils': path.resolve(__dirname, './src/utils'),
      '@types': path.resolve(__dirname, './src/types'),
      '@features': path.resolve(__dirname, './src/features'),
      '@assets': path.resolve(__dirname, './src/assets')
    }
  }
});

5. 代码分组与注释

5.1 组件内部结构

tsx
function Component() {
  // 1. Hooks (状态、副作用、上下文)
  const [count, setCount] = useState(0);
  const [user, setUser] = useState<User | null>(null);
  const { theme } = useTheme();
  const navigate = useNavigate();
  
  // 2. 计算值 (useMemo、派生状态)
  const doubleCount = useMemo(() => count * 2, [count]);
  const isAuthenticated = Boolean(user);
  
  // 3. 回调函数 (useCallback、事件处理)
  const handleIncrement = useCallback(() => {
    setCount(c => c + 1);
  }, []);
  
  const handleLogin = useCallback(async (credentials: Credentials) => {
    const user = await loginUser(credentials);
    setUser(user);
  }, []);
  
  // 4. 副作用 (useEffect、useLayoutEffect)
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);
  
  useEffect(() => {
    if (user) {
      loadUserPreferences(user.id);
    }
  }, [user]);
  
  // 5. 条件渲染
  if (!isAuthenticated) {
    return <LoginForm onLogin={handleLogin} />;
  }
  
  // 6. JSX
  return (
    <div>
      <h1>Count: {count}</h1>
      <p>Double: {doubleCount}</p>
      <button onClick={handleIncrement}>Increment</button>
    </div>
  );
}

5.2 注释规范

tsx
/**
 * 用户个人资料组件
 * 
 * 显示用户的基本信息、帖子和关注者列表
 * 
 * @example
 * ```tsx
 * <UserProfile userId="123" />
 * ```
 */
function UserProfile({ userId }: UserProfileProps) {
  // TODO: 添加错误边界
  // FIXME: 修复用户头像加载失败的问题
  // NOTE: 这个组件需要用户已登录
  
  const { user, loading } = useUser(userId);
  
  // 加载状态
  if (loading) return <Skeleton />;
  
  return (
    <div>
      {/* 用户信息区域 */}
      <UserInfo user={user} />
      
      {/* 帖子列表 */}
      <UserPosts userId={userId} />
      
      {/* 关注者列表 */}
      <UserFollowers userId={userId} />
    </div>
  );
}

/**
 * JSDoc注释示例
 * 
 * @param user - 用户对象
 * @param onUpdate - 更新回调函数
 * @returns React组件
 * 
 * @throws {Error} 当user为null时抛出错误
 * 
 * @deprecated 使用UserProfileV2代替
 * 
 * @see {@link UserProfileV2} 新版本组件
 */
function deprecatedComponent(user: User, onUpdate: (user: User) => void) {
  // ...
}

6. 常量管理

6.1 集中管理常量

typescript
// constants/API_ENDPOINTS.ts
export const API_ENDPOINTS = {
  AUTH: {
    LOGIN: '/api/auth/login',
    LOGOUT: '/api/auth/logout',
    REGISTER: '/api/auth/register',
    REFRESH: '/api/auth/refresh'
  },
  USER: {
    PROFILE: '/api/users/:id',
    UPDATE: '/api/users/:id',
    FOLLOWERS: '/api/users/:id/followers'
  },
  POSTS: {
    LIST: '/api/posts',
    DETAIL: '/api/posts/:id',
    CREATE: '/api/posts',
    UPDATE: '/api/posts/:id',
    DELETE: '/api/posts/:id'
  }
} as const;

// constants/ROUTES.ts
export const ROUTES = {
  HOME: '/',
  LOGIN: '/login',
  REGISTER: '/register',
  DASHBOARD: '/dashboard',
  PROFILE: '/profile/:id',
  SETTINGS: '/settings',
  NOT_FOUND: '/404'
} as const;

// constants/ERROR_MESSAGES.ts
export const ERROR_MESSAGES = {
  AUTH: {
    INVALID_CREDENTIALS: '用户名或密码错误',
    SESSION_EXPIRED: '会话已过期,请重新登录',
    UNAUTHORIZED: '您没有权限执行此操作'
  },
  VALIDATION: {
    REQUIRED_FIELD: '此字段为必填项',
    INVALID_EMAIL: '请输入有效的邮箱地址',
    PASSWORD_TOO_SHORT: '密码长度至少为8个字符'
  },
  NETWORK: {
    CONNECTION_FAILED: '网络连接失败',
    TIMEOUT: '请求超时',
    SERVER_ERROR: '服务器错误,请稍后重试'
  }
} as const;

// constants/THEMES.ts
export const THEMES = {
  LIGHT: {
    primary: '#007bff',
    secondary: '#6c757d',
    background: '#ffffff',
    text: '#212529'
  },
  DARK: {
    primary: '#0d6efd',
    secondary: '#6c757d',
    background: '#212529',
    text: '#ffffff'
  }
} as const;

// constants/CONFIG.ts
export const CONFIG = {
  APP_NAME: 'MyApp',
  APP_VERSION: '1.0.0',
  API_BASE_URL: process.env.REACT_APP_API_URL || 'http://localhost:3000',
  DEFAULT_LANGUAGE: 'zh-CN',
  ITEMS_PER_PAGE: 20,
  MAX_FILE_SIZE: 5 * 1024 * 1024,  // 5MB
  SUPPORTED_FILE_TYPES: ['image/jpeg', 'image/png', 'image/gif']
} as const;

6.2 枚举vs常量对象

typescript
// ✅ 使用枚举 (TypeScript特性)
enum UserRole {
  ADMIN = 'admin',
  USER = 'user',
  GUEST = 'guest'
}

enum HttpStatus {
  OK = 200,
  CREATED = 201,
  BAD_REQUEST = 400,
  UNAUTHORIZED = 401,
  NOT_FOUND = 404,
  SERVER_ERROR = 500
}

// ✅ 使用常量对象 (更灵活)
export const USER_ROLES = {
  ADMIN: 'admin',
  USER: 'user',
  GUEST: 'guest'
} as const;

export type UserRole = typeof USER_ROLES[keyof typeof USER_ROLES];

// 使用
function checkPermission(role: UserRole) {
  if (role === USER_ROLES.ADMIN) {
    // ...
  }
}

7. 配置管理

7.1 环境配置

typescript
// config/env.ts
interface EnvConfig {
  apiUrl: string;
  environment: 'development' | 'staging' | 'production';
  enableAnalytics: boolean;
  enableDebugger: boolean;
  logLevel: 'error' | 'warn' | 'info' | 'debug';
}

function getEnvConfig(): EnvConfig {
  const env = process.env.NODE_ENV || 'development';
  
  const config: Record<string, EnvConfig> = {
    development: {
      apiUrl: 'http://localhost:3000',
      environment: 'development',
      enableAnalytics: false,
      enableDebugger: true,
      logLevel: 'debug'
    },
    staging: {
      apiUrl: 'https://staging-api.myapp.com',
      environment: 'staging',
      enableAnalytics: true,
      enableDebugger: true,
      logLevel: 'info'
    },
    production: {
      apiUrl: 'https://api.myapp.com',
      environment: 'production',
      enableAnalytics: true,
      enableDebugger: false,
      logLevel: 'error'
    }
  };
  
  return config[env] || config.development;
}

export const ENV_CONFIG = getEnvConfig();

// 使用
import { ENV_CONFIG } from '@/config/env';

if (ENV_CONFIG.enableDebugger) {
  console.log('Debug mode enabled');
}

7.2 功能开关 (Feature Flags)

typescript
// config/featureFlags.ts
interface FeatureFlags {
  newDashboard: boolean;
  darkMode: boolean;
  betaFeatures: boolean;
  experimentalUI: boolean;
}

export const FEATURE_FLAGS: FeatureFlags = {
  newDashboard: process.env.REACT_APP_NEW_DASHBOARD === 'true',
  darkMode: true,
  betaFeatures: process.env.REACT_APP_BETA === 'true',
  experimentalUI: false
};

// hooks/useFeatureFlag.ts
export function useFeatureFlag(flag: keyof FeatureFlags): boolean {
  return FEATURE_FLAGS[flag];
}

// 使用
function Dashboard() {
  const isNewDashboard = useFeatureFlag('newDashboard');
  
  if (isNewDashboard) {
    return <NewDashboard />;
  }
  
  return <OldDashboard />;
}

8. 代码质量工具

8.1 ESLint配置

javascript
// .eslintrc.js
module.exports = {
  extends: [
    'react-app',
    'react-app/jest',
    'plugin:@typescript-eslint/recommended',
    'plugin:react-hooks/recommended',
    'prettier'
  ],
  plugins: ['@typescript-eslint', 'react-hooks', 'import'],
  rules: {
    // React规则
    'react/jsx-no-target-blank': 'warn',
    'react/jsx-key': 'error',
    'react/no-array-index-key': 'warn',
    
    // Hooks规则
    'react-hooks/rules-of-hooks': 'error',
    'react-hooks/exhaustive-deps': 'warn',
    
    // TypeScript规则
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-unused-vars': ['error', {
      'argsIgnorePattern': '^_',
      'varsIgnorePattern': '^_'
    }],
    
    // 导入规则
    'import/order': ['error', {
      'groups': [
        'builtin',
        'external',
        'internal',
        'parent',
        'sibling',
        'index'
      ],
      'pathGroups': [
        {
          'pattern': 'react',
          'group': 'external',
          'position': 'before'
        },
        {
          'pattern': '@/**',
          'group': 'internal',
          'position': 'before'
        }
      ],
      'pathGroupsExcludedImportTypes': ['react'],
      'newlines-between': 'always',
      'alphabetize': {
        'order': 'asc',
        'caseInsensitive': true
      }
    }],
    'import/no-duplicates': 'error',
    'import/no-cycle': 'warn'
  }
};

8.2 Prettier配置

json
// .prettierrc
{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "arrowParens": "avoid",
  "endOfLine": "lf"
}

8.3 EditorConfig

ini
# .editorconfig
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.{js,jsx,ts,tsx,json}]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false

9. 总结

良好的代码组织规范包括:

  1. 项目结构: 清晰的目录层次
  2. 组织原则: 单一职责、关注点分离
  3. 命名规范: 一致的文件和目录命名
  4. 导入导出: 规范的模块管理
  5. 代码分组: 有序的代码结构
  6. 常量管理: 集中式常量定义
  7. 配置管理: 环境和功能配置
  8. 质量工具: ESLint、Prettier等

遵循这些规范可以提高代码可维护性和团队协作效率。