一、标准 React + TS 项目目录结构(基于 Vite 构建)
通用的目录骨架(适配 React 18 + TypeScript 5 + Vite 5),后续逐个解析:
plain
your-project/
├── public/ # 静态资源(不经过打包)
├── src/
│ ├── api/ # 接口请求封装
│ ├── assets/ # 静态资源(经过打包,如图片、样式)
│ ├── components/ # 通用组件(全局/业务无关)
│ │ ├── common/ # 基础组件(按钮、输入框、弹窗)
│ │ └── business/ # 业务通用组件(如订单卡片、用户信息栏)
│ ├── hooks/ # 自定义 Hooks
│ ├── layouts/ # 布局组件(全局布局、分栏布局)
│ ├── pages/ # 页面组件(路由对应的页面)
│ │ ├── Home/ # 首页
│ │ ├── User/ # 用户模块
│ │ │ ├── List/ # 用户列表页
│ │ │ └── Detail/ # 用户详情页
│ │ └── NotFound/ # 404页面
│ ├── router/ # 路由配置
│ ├── store/ # 状态管理(Redux/TanStack Query/Zustand)
│ ├── types/ # 全局类型定义(TS)
│ ├── utils/ # 工具函数
│ ├── App.tsx # 根组件
│ ├── main.tsx # 入口文件
│ └── vite-env.d.ts # Vite TS 类型声明
├── .eslintrc.cjs # ESLint 配置
├── tsconfig.json # TypeScript 配置
├── vite.config.ts # Vite 配置
└── package.json # 依赖管理
二、各文件夹 / 文件解析(附代码 + 知识点关联)
1. public/- 静态资源(不打包)
作用
存放无需经过 Vite 打包的静态资源,直接复制到打包后的 dist 根目录,访问路径为 /文件名。
常见文件
favicon.ico:网站图标robots.txt:搜索引擎爬虫规则index.html:HTML 入口模板(Vite 会注入打包后的 JS/CSS)
知识点关联
- React 是「SPA 单页应用」,仅需一个 HTML 入口;
- 静态资源的两种加载方式:
public(绝对路径) vssrc/assets(相对路径 / 打包处理)。
2. src/assets/- 静态资源(需打包)
作用
存放需要经过 Vite 编译 / 优化的静态资源(图片、字体、全局样式),打包后会被哈希命名(防止缓存)。
示例代码(使用图片)
tsx
tsx
// src/pages/Home/Home.tsx
import React from 'react';
import logo from '@/assets/logo.png'; // 别名@指向src
const Home = () => {
return <img src={logo} alt="logo" />; // 打包后会替换为哈希后的路径
};
export default Home;
知识点关联
- 模块导入资源 :Vite/Webpack 支持将资源作为模块导入,TS 需在
vite-env.d.ts中声明类型; - 样式模块化 :若存放 CSS/SCSS,可结合
CSS Modules避免样式污染(对应 React 组件样式隔离知识点)。
3. src/types/- 全局 TS 类型定义
作用
存放项目全局的 TypeScript 接口、类型别名、枚举,统一管理类型,避免重复定义。
示例代码
ts
tsx
// src/types/user.ts
// 枚举(TS 特性,限定取值范围)
export enum UserRole {
Admin = 'admin',
User = 'user',
Guest = 'guest'
}
// 接口(定义对象结构)
export interface UserInfo {
id: string;
name: string;
age?: number; // 可选属性
role: UserRole;
createTime: Date;
}
// 接口(API 请求/响应)
export interface UserListRequest {
page: number;
size: number;
keyword?: string;
}
export interface UserListResponse {
list: UserInfo[];
total: number;
}
知识点关联
- TS 接口 / 类型别名:React 组件 Props、API 数据、状态管理的类型约束核心;
- 泛型 :后续在 Hooks/API 封装中会结合泛型(如
useRequest<T>()); - 类型扩展 :通过
interface extends实现类型复用(如BaseResponse<T>包裹所有接口响应)。
4. src/utils/- 工具函数
作用
存放通用工具函数(与业务无关),如时间格式化、防抖节流、本地存储、URL 解析等。
示例代码
ts
tsx
// src/utils/format.ts
/** 时间格式化(工具函数示例) */
export const formatDate = (date: Date | string, format = 'YYYY-MM-DD'): string => {
const targetDate = typeof date === 'string' ? new Date(date) : date;
const year = targetDate.getFullYear();
const month = String(targetDate.getMonth() + 1).padStart(2, '0');
const day = String(targetDate.getDate()).padStart(2, '0');
return format.replace('YYYY', year.toString()).replace('MM', month).replace('DD', day);
};
// src/utils/request.ts(接口请求封装,基于 Axios)
import axios from 'axios';
import type { AxiosRequestConfig, AxiosResponse } from 'axios';
import type { BaseResponse } from '@/types/common';
// 创建 Axios 实例
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // 环境变量
timeout: 10000
});
// 请求拦截器(添加 Token)
request.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器(统一处理响应)
request.interceptors.response.use(
(response: AxiosResponse<BaseResponse<any>>) => {
if (response.data.code !== 200) {
alert(response.data.message);
return Promise.reject(response.data);
}
return response.data.data;
},
(error) => {
alert('请求失败:' + error.message);
return Promise.reject(error);
}
);
// 泛型封装,约束响应类型
export const requestGet = <T>(url: string, params?: any): Promise<T> => {
return request.get(url, { params });
};
export const requestPost = <T>(url: string, data?: any): Promise<T> => {
return request.post(url, data);
};
export default request;
知识点关联
- Axios 拦截器:React 项目中接口请求的通用处理方案(统一加 Token、统一错误处理);
- 环境变量 :Vite 中
import.meta.env区分开发 / 生产环境(React 项目环境配置); - 工具函数纯函数化:无副作用,符合 React 函数式编程思想。
5. src/hooks/- 自定义 Hooks
作用
抽取组件复用逻辑(替代类组件的 mixin / 高阶组件),是 React 组件复用的核心方案。
示例代码
tsx
tsx
// src/hooks/useUser.ts(业务自定义 Hook)
import { useState, useEffect } from 'react';
import { requestGet } from '@/utils/request';
import type { UserListResponse, UserInfo } from '@/types/user';
// 泛型 Hook:获取用户列表
export const useUserList = (page: number, size: number) => {
// React Hooks 核心:状态 + 副作用
const [list, setList] = useState<UserInfo[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
// 副作用:页面/页码变化时请求数据
useEffect(() => {
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const data = await requestGet<UserListResponse>('/api/user/list', { page, size });
setList(data.list);
} catch (err) {
setError((err as Error).message);
} finally {
setLoading(false);
}
};
fetchData();
}, [page, size]); // 依赖数组:React 副作用依赖追踪
// 返回状态和方法,供组件使用
return { list, loading, error };
};
// src/hooks/useDebounce.ts(通用自定义 Hook)
import { useState, useEffect } from 'react';
// 防抖 Hook
export const useDebounce = <T>(value: T, delay = 300): T => {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// 清理函数:React 副作用的清理(防止内存泄漏)
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
};
知识点关联
- React Hooks 核心规则:只能在函数组件 / 自定义 Hook 中调用、不能条件调用;
- useState/useEffect:状态管理 + 副作用处理(React 函数组件的核心);
- 自定义 Hook 规范 :以
use开头,复用逻辑,分离组件 UI 和业务逻辑; - 泛型 Hook:适配不同类型的数据,提升复用性(TS + React 最佳实践)。
6. src/components/- 通用组件
分类
common/:基础组件(与业务无关,如 Button、Input、Modal、Loading);business/:业务通用组件(与业务强相关,如 UserCard、OrderTable)。
示例代码(基础组件)
tsx
tsx
// src/components/common/Button/Button.tsx
import React from 'react';
import './Button.scss';
// TS 约束组件 Props
interface ButtonProps {
type?: 'primary' | 'secondary' | 'danger';
size?: 'small' | 'middle' | 'large';
onClick?: () => void;
disabled?: boolean;
children: React.ReactNode; // React 内置类型:子节点类型
className?: string;
}
// 函数组件 + TS Props 约束
const Button: React.FC<ButtonProps> = ({
type = 'primary',
size = 'middle',
onClick,
disabled = false,
children,
className = ''
}) => {
// 动态拼接类名
const btnClass = `btn btn-${type} btn-${size} ${className}`;
return (
<button
className={btnClass}
onClick={onClick}
disabled={disabled}
type="button"
>
{children}
</button>
);
};
export default Button;
示例代码(业务组件)
tsx
tsx
// src/components/business/UserCard/UserCard.tsx
import React from 'react';
import type { UserInfo } from '@/types/user';
import { formatDate } from '@/utils/format';
import Button from '@/components/common/Button/Button';
import './UserCard.scss';
// 业务组件 Props
interface UserCardProps {
user: UserInfo;
onEdit: (id: string) => void; // 回调函数:组件通信
}
const UserCard: React.FC<UserCardProps> = ({ user, onEdit }) => {
return (
<div className="user-card">
<h3>{user.name}</h3>
<p>角色:{user.role}</p>
<p>创建时间:{formatDate(user.createTime)}</p>
<Button onClick={() => onEdit(user.id)} type="primary">
编辑
</Button>
</div>
);
};
export default UserCard;
知识点关联
- React 组件类型 :
React.FC(函数组件类型),内置children类型; - 组件 Props 约束:TS 接口定义 Props,明确入参类型(React + TS 核心);
- 组件通信:父组件通过 Props 传递回调函数给子组件(单向数据流);
- 组件样式隔离:SCSS 模块化、CSS Modules、Styled Components(React 样式方案);
- React.ReactNode:兼容所有可渲染类型(文本、元素、数组、null 等)。
7. src/layouts/- 布局组件
作用
封装页面通用布局(如侧边栏 + 头部 + 内容区),所有页面复用布局结构。
示例代码
tsx
tsx
// src/layouts/MainLayout/MainLayout.tsx
import React from 'react';
import { Outlet, useNavigate } from 'react-router-dom'; // React Router 知识点
import './MainLayout.scss';
import Button from '@/components/common/Button/Button';
const MainLayout: React.FC = () => {
const navigate = useNavigate(); // React Router 导航 Hook
// 退出登录逻辑
const handleLogout = () => {
localStorage.removeItem('token');
navigate('/login');
};
return (
<div className="main-layout">
{/* 侧边栏 */}
<aside className="sidebar">
<ul className="menu">
<li onClick={() => navigate('/')}>首页</li>
<li onClick={() => navigate('/user/list')}>用户管理</li>
</ul>
</aside>
{/* 头部 */}
<header className="header">
<h1>后台管理系统</h1>
<Button onClick={handleLogout} type="danger">
退出登录
</Button>
</header>
{/* 内容区:Outlet 渲染路由匹配的子页面(React Router 嵌套路由) */}
<main className="content">
<Outlet />
</main>
</div>
);
};
export default MainLayout;
知识点关联
- React Router 嵌套路由 :
Outlet是嵌套路由的 "占位符",渲染子路由组件; - useNavigate:React Router 18 导航 Hook(替代旧版 useHistory);
- 布局复用:通过路由配置关联布局,避免每个页面重复写布局代码。
8. src/router/- 路由配置
作用
集中管理路由规则(嵌套路由、路由守卫、懒加载),是 React SPA 页面跳转的核心。
示例代码
tsx
tsx
// src/router/index.tsx
import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom';
import type { RouteObject } from 'react-router-dom';
import MainLayout from '@/layouts/MainLayout/MainLayout';
import NotFound from '@/pages/NotFound/NotFound';
// 路由懒加载:React 代码分割(提升首屏加载速度)
const Home = React.lazy(() => import('@/pages/Home/Home'));
const UserList = React.lazy(() => import('@/pages/User/List/List'));
const UserDetail = React.lazy(() => import('@/pages/User/Detail/Detail'));
const Login = React.lazy(() => import('@/pages/Login/Login'));
// 路由守卫:校验登录状态
const PrivateRoute = ({ children }: { children: React.ReactNode }) => {
const hasToken = !!localStorage.getItem('token');
return hasToken ? <>{children}</> : <Navigate to="/login" />;
};
// 路由配置数组(TS 约束:RouteObject)
const routes: RouteObject[] = [
{
path: '/login',
element: <Login /> // 登录页(无需布局)
},
{
path: '/',
element: (
<PrivateRoute>
<MainLayout />
</PrivateRoute>
),
children: [
// 嵌套路由:首页
{ path: '', element: <Navigate to="/home" /> }, // 重定向
{ path: 'home', element: <Home /> },
// 用户模块嵌套路由
{ path: 'user/list', element: <UserList /> },
{ path: 'user/detail/:id', element: <UserDetail /> } // 动态路由参数
]
},
// 404 页面
{ path: '*', element: <NotFound /> }
];
// 创建路由器
const router = createBrowserRouter(routes);
// 路由组件(供 main.tsx 挂载)
const AppRouter: React.FC = () => {
return (
// React.Suspense:配合懒加载,加载中显示占位符
<React.Suspense fallback={<div>加载中...</div>}>
<RouterProvider router={router} />
</React.Suspense>
);
};
export default AppRouter;
知识点关联
- React Router 6+ 核心 :
createBrowserRouter/RouterProvider(新版路由 API); - 路由懒加载 :
React.lazy+React.Suspense(代码分割,优化性能); - 嵌套路由 :
children配置 +Outlet渲染(大型项目路由组织方式); - 路由守卫:自定义组件校验权限(登录状态、角色权限);
- 动态路由参数 :
/:id匹配动态路径,通过useParams获取(页面传参); - 重定向 :
Navigate组件(替代旧版 Redirect)。
9. src/pages/- 页面组件
作用
路由对应的页面级组件,聚合 "布局 + 组件 + Hooks + 业务逻辑",是用户最终看到的页面。
示例代码(用户列表页)
tsx
tsx
// src/pages/User/List/List.tsx
import React, { useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import UserCard from '@/components/business/UserCard/UserCard';
import Button from '@/components/common/Button/Button';
import { useUserList } from '@/hooks/useUser';
import { useDebounce } from '@/hooks/useDebounce';
import type { UserInfo } from '@/types/user';
import './List.scss';
const UserList: React.FC = () => {
// 1. 路由参数(URL 查询参数)
const [searchParams, setSearchParams] = useSearchParams();
const initialPage = Number(searchParams.get('page')) || 1;
// 2. 页面状态
const [page, setPage] = useState<number>(initialPage);
const [size, setSize] = useState<number>(10);
const [keyword, setKeyword] = useState<string>('');
const debouncedKeyword = useDebounce(keyword); // 防抖处理搜索词
// 3. 调用自定义 Hook 获取数据
const { list, loading, error } = useUserList(page, size);
// 4. 业务方法:编辑用户
const handleEdit = (id: string) => {
// 跳转到详情页(带参数)
window.location.href = `/user/detail/${id}`;
// 或用 useNavigate:navigate(`/user/detail/${id}`);
};
// 5. 分页切换
const handlePageChange = (newPage: number) => {
setPage(newPage);
// 更新 URL 查询参数
setSearchParams({ page: newPage.toString(), size: size.toString() });
};
// 6. 渲染逻辑
if (loading) return <div>加载中...</div>;
if (error) return <div className="error">{error}</div>;
return (
<div className="user-list-page">
<div className="search-bar">
<input
type="text"
placeholder="搜索用户名"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
/>
<Button type="primary">搜索</Button>
</div>
<div className="user-list">
{list.length === 0 ? (
<p>暂无用户数据</p>
) : (
list.map((user) => (
<UserCard key={user.id} user={user} onEdit={handleEdit} />
))
)}
</div>
<div className="pagination">
<Button
disabled={page === 1}
onClick={() => handlePageChange(page - 1)}
>
上一页
</Button>
<span>第 {page} 页</span>
<Button onClick={() => handlePageChange(page + 1)}>下一页</Button>
</div>
</div>
);
};
export default UserList;
知识点关联
- useSearchParams:React Router 管理 URL 查询参数(替代 URLSearchParams);
- 组件组合:页面组件聚合通用组件 / 业务组件,体现 React 组件化思想;
- 状态管理:页面级状态用 useState,跨页面状态用全局状态管理(如下文 store);
- 条件渲染:根据 loading/error 状态渲染不同内容(React 渲染逻辑);
- key 属性:列表渲染必须加唯一 key(React 虚拟 DOM 优化)。
10. src/store/- 全局状态管理(以 Zustand 为例)
作用
管理跨组件 / 跨页面的全局状态(如用户信息、全局配置),替代 Redux(更轻量)。
示例代码
tsx
tsx
// src/store/userStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import type { UserInfo } from '@/types/user';
// TS 约束 store 状态和方法
interface UserStore {
userInfo: UserInfo | null;
setUserInfo: (info: UserInfo | null) => void;
clearUserInfo: () => void;
isLogin: boolean; // 派生状态
}
// 创建 store(带持久化:localStorage)
const useUserStore = create<UserStore>()(
persist(
(set, get) => ({
userInfo: null,
// 设置用户信息
setUserInfo: (info) => set({ userInfo: info }),
// 清空用户信息
clearUserInfo: () => set({ userInfo: null }),
// 派生状态:根据 userInfo 判断是否登录
get isLogin() {
return !!get().userInfo;
}
}),
{
name: 'user-storage', // localStorage 键名
partialize: (state) => ({ userInfo: state.userInfo }) // 只持久化 userInfo
}
)
);
export default useUserStore;
知识点关联
- 全局状态管理核心:解决组件间状态共享问题(React 单向数据流的补充);
- Zustand 优势:比 Redux 简洁,无需 Provider/Reducer,TS 友好;
- 状态持久化:结合 localStorage 保存状态(页面刷新不丢失);
- 派生状态:基于现有状态计算新状态(避免重复状态)。
11. src/main.tsx - 入口文件
作用
React 应用的入口,挂载根组件到 DOM 节点。
示例代码
tsx
tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import AppRouter from '@/router';
import '@/assets/global.scss'; // 全局样式
// 挂载到 HTML 的 #root 节点(React 18 新 API)
const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(
// React.StrictMode:严格模式,检测不规范用法(开发环境)
<React.StrictMode>
<AppRouter />
</React.StrictMode>
);
知识点关联
- React 18 挂载 API :
createRoot替代旧版ReactDOM.render; - StrictMode:开发环境下双重渲染组件,检测副作用 / 不规范代码;
- 全局样式:入口文件导入全局样式,作用于整个应用。
12. 配置文件(tsconfig.json/vite.config.ts)
tsconfig.json(核心配置)
json
json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* 路径别名 */
"paths": {
"@/*": ["./src/*"] // 别名@指向src,简化导入路径
},
/* 模块解析 */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx", // React JSX 编译模式
/* 类型检查 */
"strict": true, // 严格模式(TS 最佳实践)
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"], // 包含的 TS 文件
"references": [{ "path": "./tsconfig.node.json" }]
}
vite.config.ts(核心配置)
ts
ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
// 路径别名配置(和 tsconfig.json 对应)
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
},
// 开发服务器配置
server: {
port: 3000,
open: true,
proxy: {
// 接口代理(解决跨域)
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
});
知识点关联
- TS 路径别名:简化导入路径,避免相对路径层级混乱;
- Vite 代理:解决开发环境接口跨域问题(前端跨域核心方案);
- React 编译插件 :
@vitejs/plugin-react处理 JSX/TSX 编译。
三、核心知识点关联总结
| 文件夹 | 核心 React 知识点 | 核心 TS 知识点 |
|---|---|---|
| components | 组件化、Props、children、样式隔离 | 接口约束 Props、React.ReactNode |
| hooks | Hooks 规则、useState/useEffect、自定义 Hook | 泛型 Hook、类型推导 |
| router | 路由懒加载、嵌套路由、路由守卫、导航 | RouteObject 类型、动态参数类型 |
| store | 全局状态管理、状态持久化 | 接口约束 store、派生状态 |
| pages | 组件组合、条件渲染、列表渲染(key) | 页面状态类型、事件处理类型 |
| api/utils | 接口封装、副作用处理、工具函数 | 泛型请求函数、工具类型 |
最终总结
- 目录设计原则:按「功能 / 职责」划分(组件 / 页面 / Hooks / 路由 / 状态),符合 "高内聚、低耦合";
- React 核心落地 :组件化、Hooks、路由、状态管理是四大核心,对应目录中的
components/hooks/router/store; - TS 核心价值:全链路类型约束(Props / 接口 / 状态 / 工具函数),避免类型错误,提升可维护性;
- 性能优化点:路由懒加载、组件复用、key 属性、防抖节流(分散在各目录中);
- 工程化规范:路径别名、ESLint、环境变量、静态资源管理(配置文件 + 目录结构体现)。