🏗️ 推荐的目录结构
src/
├── app/ # 应用核心配置
│ ├── routes/ # 路由配置
│ ├── store/ # Redux/Zustand 状态管理
│ └── providers/ # 全局 Providers 组合
├── features/ # 功能模块(核心业务)
│ ├── auth/
│ │ ├── components/ # 模块私有组件
│ │ ├── hooks/ # 模块私有 hooks
│ │ ├── services/ # API 调用
│ │ ├── types/ # TypeScript 类型
│ │ ├── utils/ # 工具函数
│ │ └── index.ts # 公共导出
│ ├── dashboard/
│ └── user/
├── components/ # 共享通用组件
│ ├── ui/ # 基础 UI 组件(Button, Input)
│ ├── layout/ # 布局组件(Header, Sidebar)
│ └── shared/ # 业务共享组件(UserAvatar, DataTable)
├── hooks/ # 全局共享 hooks
├── lib/ # 第三方库配置
│ ├── api/ # HTTP 客户端配置
│ └── react-query.ts # React Query 配置
├── utils/ # 纯函数工具
├── types/ # 全局类型定义
├── constants/ # 常量定义
├── styles/ # 全局样式
└── assets/ # 静态资源
📁 详细说明与示例
1. features/ - 功能模块(核心)
这是最重要的目录,按业务功能组织代码:
typescript
// features/auth/types/index.ts
export interface User {
id: string;
email: string;
name: string;
role: 'admin' | 'user';
}
// features/auth/services/authService.ts
import { api } from '@/lib/api';
export const authService = {
login: (credentials: { email: string; password: string }) =>
api.post<User>('/auth/login', credentials),
logout: () => api.post('/auth/logout'),
getCurrentUser: () => api.get<User>('/auth/me'),
};
// features/auth/hooks/useAuth.ts
import { useQuery, useMutation } from '@tanstack/react-query';
import { authService } from '../services';
export function useAuth() {
const { data: user, isLoading } = useQuery({
queryKey: ['auth', 'user'],
queryFn: authService.getCurrentUser,
});
const loginMutation = useMutation({
mutationFn: authService.login,
onSuccess: (data) => {
// 处理登录成功
},
});
return {
user,
isLoading,
login: loginMutation.mutate,
isLoggingIn: loginMutation.isPending,
};
}
// features/auth/components/LoginForm.tsx
import { useAuth } from '../hooks/useAuth';
export function LoginForm() {
const { login, isLoggingIn } = useAuth();
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
login({
email: formData.get('email') as string,
password: formData.get('password') as string,
});
};
return (
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit" disabled={isLoggingIn}>
{isLoggingIn ? '登录中...' : '登录'}
</button>
</form>
);
}
// features/auth/index.ts - 统一导出
export { LoginForm } from './components/LoginForm';
export { useAuth } from './hooks/useAuth';
export type { User } from './types';
2. components/ - 共享组件
tsx
// components/ui/Button/Button.tsx
import { cva, type VariantProps } from 'class-variance-authority';
const buttonVariants = cva(
'rounded-md font-medium transition-colors',
{
variants: {
variant: {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
danger: 'bg-red-600 text-white hover:bg-red-700',
},
size: {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
);
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
loading?: boolean;
}
export const Button: React.FC<ButtonProps> = ({
variant,
size,
loading,
children,
disabled,
...props
}) => {
return (
<button
className={buttonVariants({ variant, size })}
disabled={disabled || loading}
{...props}
>
{loading && <Spinner className="mr-2" />}
{children}
</button>
);
};
3. app/store/ - 状态管理
typescript
// app/store/store.ts (Redux Toolkit 示例)
import { configureStore } from '@reduxjs/toolkit';
import { authReducer } from '@/features/auth/store/authSlice';
import { uiReducer } from './slices/uiSlice';
export const store = configureStore({
reducer: {
auth: authReducer,
ui: uiReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// app/store/hooks.ts - 类型化的 hooks
import { useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();
4. lib/api/ - HTTP 客户端配置
typescript
// lib/api/client.ts
import axios from 'axios';
import { authService } from '@/features/auth/services';
const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
timeout: 30000,
});
// 请求拦截器
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器
apiClient.interceptors.response.use(
(response) => response.data,
async (error) => {
if (error.response?.status === 401) {
// 刷新 token
await authService.refreshToken();
return apiClient(error.config);
}
return Promise.reject(error);
}
);
export { apiClient };
5. app/providers/ - 全局 Providers
tsx
// app/providers/AppProviders.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Provider as ReduxProvider } from 'react-redux';
import { store } from '../store';
import { ThemeProvider } from './ThemeProvider';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5分钟
retry: 1,
},
},
});
interface AppProvidersProps {
children: React.ReactNode;
}
export function AppProviders({ children }: AppProvidersProps) {
return (
<ReduxProvider store={store}>
<QueryClientProvider client={queryClient}>
<ThemeProvider>
{children}
</ThemeProvider>
</QueryClientProvider>
</ReduxProvider>
);
}
🎯 大型项目的最佳实践
1. 模块独立性原则
每个 feature 应该是自包含的,可以独立开发和测试:
features/
├── product/
│ ├── components/ # 只服务于 product 模块
│ ├── hooks/ # 只服务于 product 模块
│ ├── services/ # product 相关的 API
│ ├── types/ # product 相关的类型
│ └── __tests__/ # product 模块的测试
2. 导入路径规范
使用路径别名避免深层相对路径:
json
// tsconfig.json
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"],
"@features/*": ["./src/features/*"],
"@components/*": ["./src/components/*"],
"@utils/*": ["./src/utils/*"],
"@hooks/*": ["./src/hooks/*"]
}
}
}
使用示例:
typescript
// ❌ 避免
import Button from '../../../components/ui/Button';
// ✅ 推荐
import { Button } from '@/components/ui/Button';
import { useAuth } from '@features/auth';
3. 文件名命名约定
typescript
// 组件文件:PascalCase
LoginForm.tsx
UserProfile.tsx
// Hook 文件:camelCase with 'use' prefix
useAuth.ts
useDebounce.ts
// 工具函数:camelCase
formatDate.ts
validateEmail.ts
// 类型文件:camelCase or PascalCase
user.types.ts
api.types.ts
// 常量文件:UPPER_CASE
API_CONSTANTS.ts
4. 测试文件组织
typescript
// 测试文件紧邻源文件
features/
├── auth/
│ ├── components/
│ │ ├── LoginForm.tsx
│ │ └── LoginForm.test.tsx # 单元测试
│ ├── hooks/
│ │ ├── useAuth.ts
│ │ └── useAuth.test.ts
│ └── __tests__/ # 集成测试
│ └── auth.integration.test.ts
📊 不同规模项目的结构对比
| 项目规模 | 目录结构 | 何时采用 |
|---|---|---|
| 小型 (1-2周) | components/, pages/, hooks/, utils/ |
原型验证、内部工具 |
| 中型 (1-3月) | 添加 features/, services/, types/ |
SaaS 产品、B端应用 |
| 大型 (3月+) | 完整结构 + 微前端 | 企业级应用、多团队协作 |