React 开发入门 & 熟练使用 - 资深架构师实战指南
目录
一、前端核心基石
1.1 JavaScript 核心
1.1.1 ES6+ 语法(生产环境必备)
【架构师视角】ES6+ 不是语法糖,是现代 JS 工程的基石
✅ 必须精通的特性
| 特性 | 生产场景 | 性能影响 | 安全考量 |
|---|---|---|---|
| 箭头函数 | 回调函数、组件定义、事件处理 | 无性能损耗 | ⚠️ 避免 this 误用 |
| 解构赋值 | Props 解构、API 响应处理、状态提取 | 微小优化 | ✅ 减少临时变量 |
| 模板字符串 | 动态拼接 URL、HTML 片段、日志输出 | 无差异 | ⚠️ XSS 风险需转义 |
| Promise/async-await | 异步数据请求、文件操作、定时任务 | 比 callback 更高效 | ✅ 错误处理更清晰 |
| 模块化 (ESM) | 组件拆分、工具函数、配置文件 | Tree-shaking 友好 | ✅ 作用域隔离 |
🎯 实战代码示例
// ❌ 反面教材:回调地狱 + this 问题
class OldComponent extends React.Component {
constructor(props) {
super(props);
this.state = { data: null };
// 必须手动绑定 this
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
fetch('/api/data')
.then(response => response.json())
.then(data => {
this.setState({ data });
})
.catch(error => console.error(error));
}
}
// ✅ 最佳实践:箭头函数 + async/await + 解构
const ModernComponent = ({ userId }) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchData = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error('Network error');
const { user, permissions } = await response.json();
setData(user);
} catch (err) {
setError(err.message);
// 生产环境应接入错误监控(如 Sentry)
console.error('[API Error]', err);
} finally {
setLoading(false);
}
}, [userId]);
useEffect(() => {
fetchData();
}, [fetchData]);
return (
<div className="user-profile">
{loading && <Skeleton />}
{error && <ErrorAlert message={error} />}
{data && <UserCard {...data} />}
</div>
);
};
🔒 安全性要点
// ⚠️ 危险:模板字符串直接插入用户输入(XSS 风险)
const BadExample = ({ userInput }) => (
<div dangerouslySetInnerHTML={{ __html: `${userInput}` }} />
);
// ✅ 安全:React 自动转义 + 显式验证
const SafeExample = ({ userInput }) => {
const sanitizedInput = DOMPurify.sanitize(userInput); // 使用消毒库
return <div>{sanitizedInput}</div>; // React 默认转义
};
1.1.2 闭包(Closure)—— React Hooks 的底层原理
【架构师洞察】不理解闭包,就无法真正掌握 useEffect 和 useState
📚 核心概念
// 闭包的本质:函数记住并访问其词法作用域
function createCounter() {
let count = 0; // 私有变量,外部无法直接访问
return {
increment: () => ++count, // 闭包引用 count
decrement: () => --count,
getCount: () => count,
reset: () => { count = 0; }
};
}
// 应用到 React State
const Counter = () => {
const [count, setCount] = useState(0); // useState 内部就是闭包实现
// 每次 render 都会创建新的闭包
const handleIncrement = () => {
setCount(prev => prev + 1); // 函数式更新,避免闭包陷阱
};
return <button onClick={handleIncrement}>{count}</button>;
};
⚠️ 经典闭包陷阱(Stale Closure)
// ❌ 错误示例:闭包捕获了旧的 state
const TimerBad = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // count 永远是 0!闭包陷阱
}, 1000);
return () => clearInterval(id);
}, []); // 空依赖导致闭包过期
return <div>{count}</div>;
};
// ✅ 正确方案 1:使用函数式更新
const TimerGood1 = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1); // 接收最新 state
}, 1000);
return () => clearInterval(id);
}, []);
return <div>{count}</div>;
};
// ✅ 正确方案 2:使用 useRef 保持可变引用
const TimerGood2 = () => {
const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() => {
const id = setInterval(() => {
countRef.current += 1;
setCount(countRef.current);
}, 1000);
return () => clearInterval(id);
}, []);
return <div>{count}</div>;
};
1.1.3 原型链与继承(理解 React 类组件)
【现代视角】虽然函数组件是主流,但理解原型链有助于阅读 legacy 代码
// React 类组件的继承机制
class MyComponent extends React.Component {
constructor(props) {
super(props); // 必须调用父类构造器
this.state = { value: props.initialValue };
}
// 方法挂载在原型上(所有实例共享)
handleClick() {
this.setState(prev => ({ value: prev.value + 1 }));
}
render() {
// render 是 React 触发的,this 已绑定
return (
<button onClick={() => this.handleClick()}>
{this.state.value}
</button>
);
}
}
// 原型链示意图:
// MyComponent 实例 → MyComponent.prototype → Component.prototype → Object.prototype
1.1.4 异步编程(Event Loop 与微任务/宏任务)
【性能关键】理解 Event Loop 才能优化渲染性能
┌─────────────────────────────────────┐
│ Call Stack(调用栈) │
│ ┌─────────────────────────────┐ │
│ │ render() → setState() │ │ ← 同步代码
│ └─────────────────────────────┘ │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Web APIs(异步操作) │
│ • setTimeout / setInterval │
│ • fetch / XMLHttpRequest │
│ • DOM 事件 │
│ • Promise.then() → Microtask Queue │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Task Queues(任务队列) │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Microtask │ │ Macrotask │ │
│ │ (Promise) │ │ (setTimeout) │ │
│ │ 优先级更高 │ │ │ │
│ └─────────────┘ └──────────────┘ │
└─────────────────────────────────────┘
│
▼
Event Loop(事件循环)
🎯 React 渲染中的异步应用
// React 18 的 Concurrent Mode 利用 microtask 优先级
const App = () => {
const [isPending, startTransition] = useTransition();
const [searchTerm, setSearchTerm] = useState('');
const handleSearch = (value) => {
// 紧急更新:立即响应用户输入
setSearchTerm(value);
// 非紧急更新:降低优先级,避免阻塞交互
startTransition(() => {
performExpensiveSearch(value);
});
};
return <SearchInput onChange={handleSearch} isLoading={isPending} />;
};
1.2 CSS 基础与工程化
1.2.1 现代 CSS 布局体系
【架构师建议】Flexbox 用于一维布局,Grid 用于二维布局,避免滥用 absolute 定位
Flexbox(弹性盒子)—— 一维布局之王
/* 经典的 Header 布局 */
.header {
display: flex;
justify-content: space-between; /* 主轴对齐 */
align-items: center; /* 交叉轴对齐 */
padding: 0 24px;
height: 64px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.logo {
flex-shrink: 0; /* 防止 Logo 被压缩 */
}
.nav-menu {
display: flex;
gap: 32px; /* 现代间距方案,替代 margin */
}
.user-actions {
margin-left: auto; /* 推到右侧 */
}
Grid(网格布局)—— 二维布局利器
/* 后台管理系统经典布局 */
.dashboard {
display: grid;
grid-template-columns: 240px 1fr; /* 侧边栏 + 内容区 */
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"sidebar footer";
min-height: 100vh;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
/* 响应式适配 */
@media (max-width: 768px) {
.dashboard {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"footer";
}
.sidebar { display: none; } /* 或改为抽屉模式 */
}
1.2.2 BEM 命名规范(Block Element Modifier)
【团队协作标准】BEM 解决 CSS 命名冲突,提升可维护性
/* Block:独立的功能块 */
.card { }
/* Element:块的组成部分 */
.card__title { }
.card__content { }
.card__footer { }
/* Modifier:状态或变体 */
.card--featured { }
.card--disabled { }
.card__title--large { }
// React 中配合 CSS Modules 使用
import styles from './Card.module.css';
const Card = ({ isFeatured, title, children }) => (
<div className={`${styles.card} ${isFeatured ? styles['card--featured'] : ''}`}>
<h3 className={styles.card__title}>{title}</h3>
<div className={styles.card__content}>{children}</div>
</div>
);
1.2.3 响应式设计策略
【移动优先】从 Mobile First 到 Progressive Enhancement
/* 移动优先:默认样式针对小屏 */
.container {
width: 100%;
padding: 16px;
}
/* 平板设备 */
@media (min-width: 768px) {
.container {
max-width: 720px;
margin: 0 auto;
padding: 24px;
}
}
/* 桌面设备 */
@media (min-width: 1024px) {
.container {
max-width: 1200px;
padding: 32px;
}
}
/* 大屏设备 */
@media (min-width: 1440px) {
.container {
max-width: 1400px;
}
}
🎯 React 中的响应式方案对比
| 方案 | 适用场景 | 性能 | 可维护性 |
|---|---|---|---|
| CSS Media Queries | 简单断点切换 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| CSS-in-JS (styled-components) | 动态主题、复杂逻辑 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Tailwind CSS | 快速原型、原子化开发 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| CSS Modules + PostCSS | 中大型项目 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
1.3 浏览器原理与网络
1.3.1 浏览器渲染流程(React Virtual DOM 的存在意义)
用户操作 → JavaScript → Virtual DOM → Diff Algorithm → Real DOM Update
↓
[Batching & Reconciliation]
↓
Layout → Paint → Composite → Display
🎯 关键渲染路径优化
// ❌ 强制同步布局(Layout Thrashing)
function badPerformance() {
const elements = document.querySelectorAll('.item');
elements.forEach(el => {
const height = el.offsetHeight; // 读取 → 触发 reflow
el.style.height = `${height * 2}px`; // 写入 → 再次触发 reflow
}); // 循环 n 次 = n 次 reflow
}
// ✅ 批量读写(React 自动优化这一点)
function goodPerformance() {
const elements = document.querySelectorAll('.item');
// 第一轮:批量读取
const heights = Array.from(el => el.offsetHeight);
// 第二轮:批量写入
elements.forEach((el, i) => {
el.style.height = `${heights[i] * 2}px`;
}); // 只触发 2 次 reflow
}
1.3.2 跨域解决方案(CORS 配置)
【安全第一】跨域不是 bug,是浏览器安全机制
// 前端请求配置
const apiClient = axios.create({
baseURL: process.env.REACT_APP_API_BASE_URL,
timeout: 10000,
withCredentials: true, // 搭带 Cookie(需要后端配合)
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
});
// 请求拦截器:添加认证 Token
apiClient.interceptors.request.use(config => {
const token = localStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器:统一错误处理
apiClient.interceptors.response.use(
response => response.data,
error => {
if (error.response?.status === 401) {
// Token 过期,跳转登录
window.location.href = '/login';
}
return Promise.reject(error);
}
);
🔒 CORS 安全配置(服务端)
# Nginx 配置示例
location /api/ {
# 仅允许指定域名跨域(禁止 *)
add_header Access-Control-Allow-Origin https://yourdomain.com always;
# 允许的请求方法
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, OPTIONS' always;
# 允许的请求头
add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization' always;
# 允许携带凭证
add_header Access-Control-Allow-Credentials true always;
# 预检请求缓存时间(秒)
add_header Access-Control-Max-Age 1728000 always;
# 处理 OPTIONS 预检请求
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://backend_server;
}
1.3.3 本地存储方案选型
| 存储方式 | 容量 | 持久性 | 安全等级 | 适用场景 |
|---|---|---|---|---|
| Cookie | 4KB | 可设置过期 | ⚠️ 易被窃取 | Session ID、CSRF Token |
| LocalStorage | 5-10MB | 永久(除非清除) | ⚠️ 明文存储 | 用户偏好设置、非敏感缓存 |
| SessionStorage | 5-10MB | 关闭标签页即清除 | ⚠️ 明文存储 | 表单临时数据、页面状态 |
| IndexedDB | 250MB+ | 永久 | ✅ 可加密 | 离线存储、大型数据缓存 |
🔒 安全存储实践
// ✅ 敏感数据加密存储(使用 crypto-js)
import CryptoJS from 'crypto-js';
const SECRET_KEY = process.env.REACT_APP_ENCRYPTION_KEY;
const secureStorage = {
setItem(key, value) {
const encrypted = CryptoJS.AES.encrypt(JSON.stringify(value), SECRET_KEY).toString();
localStorage.setItem(key, encrypted);
},
getItem(key) {
const encrypted = localStorage.getItem(key);
if (!encrypted) return null;
try {
const bytes = CryptoJS.AES.decrypt(encrypted, SECRET_KEY);
return JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
} catch {
console.error('Decryption failed');
return null;
}
},
removeItem(key) {
localStorage.removeItem(key);
}
};
// 使用示例
secureStorage.setItem('user_token', { token: 'xxx', expires: Date.now() + 3600000 });
const userData = secureStorage.getItem('user_token');
1.4 开发工具链
1.4.1 Git 工作流(团队协作基础)
【业界标准】Git Flow 或 GitHub Flow,杜绝直接 push main
main (生产环境)
├── develop (开发分支)
│ ├── feature/user-login (功能分支)
│ ├── feature/payment-system
│ └── hotfix/security-patch (紧急修复)
└── release/v1.2.0 (发布分支)
📝 Git 命令速查
# 日常开发流程
git checkout -b feature/new-feature # 创建功能分支
git add . # 暂存更改
git commit -m "feat: add user login" # 提交(遵循 Conventional Commits)
git push origin feature/new-feature # 推送远程
git pull request # 创建 PR 进行 Code Review
# 合并代码
git checkout develop
git merge --no-ff feature/new-feature # 保留合并历史
git branch -d feature/new-feature # 删除本地分支
# 紧急修复
git checkout main
git checkout -b hotfix/critical-bug
# ... 修复 ...
git merge --no-ff hotfix/critical-bug
git tag -a v1.2.1 -m "Release version 1.2.1"
1.4.2 Chrome DevTools 调试技巧
【效率提升】掌握这些技巧,调试效率翻倍
🎯 React 专用调试
1. Components 面板:
- 查看 Props/State/Hooks 实时值
- 直接修改 State 测试 UI 变化
- 追踪组件渲染原因(为什么重新渲染?)
2. Profiler 面板:
- 录制渲染性能
- 识别未优化的组件(红色标记)
- 分析渲染耗时分布
3. Console 面板:
- $r → 当前选中的 React 组件实例
- $0 → 当前选中的 DOM 元素
💡 高效调试命令
// 在控制台中使用
console.table(users); // 表格化输出对象数组
console.group('API Request'); // 分组日志
console.log('URL:', url);
console.log('Params:', params);
console.groupEnd();
console.time('Fetch Data'); // 计时器
fetch('/api/data').then(() => {
console.timeEnd('Fetch Data'); // 输出耗时
});
// 条件断点(在 Sources 面板右键行号)
// 只有当条件满足时才暂停
users.length > 0
二、React 核心基础 API
2.1 核心概念深度解析
2.1.1 JSX 语法本质(Syntactic Sugar)
【底层原理】JSX 只是 React.createElement() 的语法糖
// 你写的 JSX
const element = <h1 className="greeting">Hello, world!</h1>;
// Babel 编译后的真实代码
const element = React.createElement(
'h1',
{ className: 'greeting' },
'Hello, world!'
);
// 最终生成的 JavaScript 对象(Virtual DOM)
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
},
key: null,
ref: null
};
🎯 JSX 使用规范
// ✅ 最佳实践
const UserProfile = ({ user, onEdit }) => (
<article className="user-card">
<header>
<img
src={user.avatar}
alt={`${user.name}的头像`} // 动态 alt 文本(无障碍)
loading="lazy" // 图片懒加载(性能优化)
/>
<h2>{user.name}</h2>
</header>
<section className="user-info">
<p><strong>邮箱:</strong>{user.email}</p>
<p><strong>角色:</strong>{user.role}</p>
</section>
<footer>
<button onClick={() => onEdit(user.id)}>
编辑资料
</button>
</footer>
</article>
);
// ❌ 避免的反模式
const BadExample = () => (
<div>
{/* 不要在 JSX 中写复杂逻辑 */}
{items.map(item => {
if (item.type === 'special') {
return <SpecialItem key={item.id} {...item} />;
} else if (item.type === 'normal') {
return <NormalItem key={item.id} {...item} />;
}
// 缺少 fallback,可能导致 undefined 渲染
})}
</div>
);
2.1.2 虚拟 DOM 与 Diff 算法
【性能核心】理解 Virtual DOM 才能写出高性能组件
🔄 Diff 算法的三个假设(O(n) 复杂度的基础)
- 不同类型的元素 → 不同的树
// 完全替换,不比较子节点 <div>...</div> → <span>...</span> - 通过
key标识同级子元素// ✅ 使用稳定的唯一标识 <TodoList items={todos.map(todo => ( <TodoItem key={todo.id} {...todo} /> ))} /> // ❌ 使用索引作为 key(反模式) <TodoList items={todos.map((todo, index) => ( <TodoItem key={index} {...todo} /> // 插入/删除会导致错误复用 ))} /> - 只同层比较,不跨层
// 不会跨层级 diff <div> <A /> <B /> ← 只和原来的 B 比较 </div>
📊 Diff 算法实战演示
// 初始状态
const oldVDOM = ['A', 'B', 'C', 'D'];
// 新状态:在头部插入 E
const newVDOM = ['E', 'A', 'B', 'C', 'D'];
// ❌ 如果 key 用索引(0,1,2,3,4):
// A(0→1), B(1→2), C(2→3), D(3→4), 新增 E(0)
// 结果:4 次更新 + 1 次新增 = 5 次 DOM 操作
// ✅ 如果 key 用唯一 id(id_A, id_B, ...):
// 新增 E(id_E),其余保持不变
// 结果:1 次新增 = 1 次 DOM 操作(性能提升 5 倍!)
2.1.3 函数组件 vs 类组件(2026 年的选择)
【架构决策】函数组件 + Hooks 是绝对主流,类组件仅用于维护 Legacy 代码
| 维度 | 函数组件(推荐) | 类组件(Legacy) |
|---|---|---|
| 代码量 | 少 30-50% | 冗长 |
| this 指向 | 无此问题 | 容易出错 |
| 逻辑复用 | 自定义 Hooks | HOC / Render Props |
| Tree Shaking | ✅ 支持 | ❌ 不支持 |
| 性能 | 相同(React 内部优化) | 相同 |
| 未来支持 | ✅ 持续迭代 | ❌ 维护模式 |
🔄 迁移指南(类组件 → 函数组件)
// ❌ 旧版类组件
class CounterClass extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.increment = this.increment.bind(this);
}
increment() {
this.setState(({ count }) => ({ count: count + 1 }));
}
componentDidMount() {
document.title = `Count: ${this.state.count}`;
}
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `Count: ${this.state.count}`;
}
}
render() {
return (
<button onClick={this.increment}>
Count: {this.state.count}
</button>
);
}
}
// ✅ 现代函数组件
const CounterFunction = () => {
const [count, setCount] = useState(0);
// componentDidMount + componentDidUpdate 合并
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]); // 依赖数组明确声明
const increment = useCallback(() => {
setCount(c => c + 1);
}, []); // 箭头函数无需绑定 this
return (
<button onClick={increment}>
Count: {count}
</button>
);
};
2.2 渲染逻辑最佳实践
2.2.1 条件渲染(Conditional Rendering)
【可读性优先】选择合适的条件渲染方式
🎯 场景化方案选择
// 1. 三元运算符:简单的二选一场景
const StatusBadge = ({ status }) => (
<span className={`badge badge--${status}`}>
{status === 'active' ? '启用' : '禁用'}
</span>
);
// 2. 逻辑与 (&&):可选渲染(注意 falsy 值陷阱)
const UserGreeting = ({ user }) => (
<div>
{user && <WelcomeMessage name={user.name} />} // ⚠️ user=0 时不会渲染
{!!user && <WelcomeMessage name={user.name} />} // ✅ 显式转为布尔值
</div>
);
// 3. 立即执行函数:复杂条件逻辑(避免过度嵌套)
const ComplexCondition = ({ userType, isLoggedIn, hasPermission }) => (
<div>
{(() => {
if (!isLoggedIn) return <LoginPrompt />;
if (userType === 'admin') return <AdminPanel />;
if (hasPermission) return <UserDashboard />;
return <AccessDenied />;
})()}
</div>
);
// 4. 抽取为独立组件(最推荐):复杂渲染逻辑
const ContentSwitcher = ({ userType, isLoggedIn, hasPermission }) => {
if (!isLoggedIn) return <LoginPrompt />;
if (userType === 'admin') return <AdminPanel />;
if (hasPermission) return <UserDashboard />;
return <AccessDenied />;
};
const App = () => (
<Layout>
<ContentSwitcher {...props} />
</Layout>
);
⚠️ 常见陷阱
// ❌ 陷阱 1:数字 0 被视为 falsy
const ItemCount = ({ count }) => (
<div>
{count && <span>共 {count} 项</span>} {/* count=0 时不显示 */}
</div>
);
// ✅ 修复方案
const ItemCountFixed = ({ count }) => (
<div>
{count > 0 && <span>共 {count} 项</span>}
{count === 0 && <span>暂无数据</span>}
</div>
);
// ❌ 陷阱 2:三元运算符嵌套过深
const BadNested = ({ status, role, isNew }) => (
<div>
{status === 'loading'
? <Spinner />
: status === 'error'
? <Error />
: role === 'admin'
? <Admin />
: isNew
? <NewUser />
: <NormalUser />
}
</div>
);
// ✅ 修复:提前返回或使用对象映射
const ComponentMap = {
loading: Spinner,
error: Error,
admin: Admin,
newUser: NewUser,
default: NormalUser,
};
const GoodMapping = ({ status, role, isNew }) => {
const getComponentKey = () => {
if (status === 'loading') return 'loading';
if (status === 'error') return 'error';
if (role === 'admin') return 'admin';
if (isNew) return 'newUser';
return 'default';
};
const Component = ComponentMap[getComponentKey()];
return <Component />;
};
2.2.2 列表渲染与 Key 原理
【性能关键】key 是 React Diff 算法的核心优化点
🎯 Key 的三大原则
- 稳定性:同一数据在不同渲染中 key 不变
- 唯一性:兄弟元素之间 key 不能重复
- 可预测性:key 应该来自数据本身,而非随机生成
// ✅ 最佳实践:使用业务主键
const TodoList = ({ todos }) => (
<ul>
{todos.map(todo => (
<li key={todo.id}> {/* 数据库主键或唯一标识 */}
{todo.text}
</li>
))}
</ul>
);
// ⚠️ 可接受:组合字段作为 key(当没有单一主键时)
const UserList = ({ users }) => (
<div>
{users.map(user => (
<UserProfile
key={`${user.departmentId}-${user.employeeId}`} // 组合唯一键
{...user}
/>
))}
</div>
);
// ❌ 绝对禁止:使用 Math.random()
const TerribleExample = ({ items }) => (
<ul>
{items.map(item => (
<li key={Math.random()}> {/* 每次渲染都生成新 key! */}
{item.name}
</li>
))}
</ul>
);
// ❌ 避免使用:数组索引(仅在静态列表中可用)
const StaticNav = () => (
<nav>
{['首页', '产品', '关于我们'].map((text, index) => (
<a key={index} href="#">{text}</a> {/* 静态列表可以接受 */}
))}
</nav>
);
📊 Key 对 Diff 算法的影响可视化
// 场景:待办事项列表,在中间插入新项目
// 初始状态
const before = [
{ id: 'a', text: '学习 React' },
{ id: 'b', text: '学习 Vue' },
{ id: 'c', text: '学习 Angular' },
];
// 操作:在位置 0 插入新项目
const after = [
{ id: 'x', text: '学习 HTML' }, // 新增
{ id: 'a', text: '学习 React' },
{ id: 'b', text: '学习 Vue' },
{ id: 'c', text: '学习 Angular' },
];
// 方案 1:使用索引 key (0, 1, 2 → 0, 1, 2, 3)
// Diff 结果:
// - key=0: text '学习 React' → '学习 HTML' (更新)
// - key=1: text '学习 Vue' → '学习 React' (更新)
// - key=2: text '学习 Angular' → '学习 Vue' (更新)
// - 新增 key=3: '学习 Angular'
// 总计:3 次更新 + 1 次新增 = 4 次 DOM 操作 ❌
// 方案 2:使用 id key ('a', 'b', 'c' → 'x', 'a', 'b', 'c')
// Diff 结果:
// - 新增 key='x': '学习 HTML'
// - key='a', 'b', 'c': 内容未变,保留原位
// 总计:1 次新增 = 1 次 DOM 操作 ✅ (性能提升 400%!)
2.2.3 表单受控组件与非受控组件
【表单处理】受控组件是 React 的推荐做法
🎯 受控组件(Controlled Components)
// ✅ 推荐:完全由 React state 控制的表单
const RegistrationForm = () => {
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
confirmPassword: '',
agreeTerms: false,
});
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
// 统一的变更处理器
const handleChange = (field) => (e) => {
const { value, type, checked } = e.target;
const newValue = type === 'checkbox' ? checked : value;
setFormData(prev => ({
...prev,
[field]: newValue,
}));
// 实时校验(用户输入时)
if (touched[field]) {
validateField(field, newValue);
}
};
// 失去焦点时标记为已触摸
const handleBlur = (field) => () => {
setTouched(prev => ({ ...prev, [field]: true }));
validateField(field, formData[field]);
};
// 字段校验逻辑
const validateField = (field, value) => {
let error = '';
switch (field) {
case 'username':
if (!value) error = '用户名不能为空';
else if (value.length < 3) error = '用户名至少 3 个字符';
break;
case 'email':
if (!value) error = '邮箱不能为空';
else if (!/\S+@\S+\.\S+/.test(value)) error = '邮箱格式不正确';
break;
case 'password':
if (!value) error = '密码不能为空';
else if (value.length < 8) error = '密码至少 8 个字符';
break;
case 'confirmPassword':
if (value !== formData.password) error = '两次密码不一致';
break;
}
setErrors(prev => ({ ...prev, [field]: error }));
return !error;
};
// 提交处理
const handleSubmit = async (e) => {
e.preventDefault();
// 标记所有字段为已触摸
const allFields = Object.keys(formData);
setTouched(allFields.reduce((acc, field) => ({ ...acc, [field]: true }), {}));
// 校验所有字段
const isValid = allFields.every(field => validateField(field, formData[field]));
if (!isValid || !formData.agreeTerms) {
alert('请检查表单填写是否正确');
return;
}
try {
await api.register(formData);
toast.success('注册成功!');
// 跳转到登录页...
} catch (err) {
toast.error(err.message);
}
};
return (
<form onSubmit={handleSubmit} className="registration-form">
<FormField
label="用户名"
required
error={touched.username && errors.username}
>
<input
type="text"
value={formData.username}
onChange={handleChange('username')}
onBlur={handleBlur('username')}
placeholder="请输入用户名"
autoComplete="username"
/>
</FormField>
<FormField
label="邮箱"
required
error={touched.email && errors.email}
>
<input
type="email"
value={formData.email}
onChange={handleChange('email')}
onBlur={handleBlur('email')}
placeholder="example@email.com"
autoComplete="email"
/>
</FormField>
<FormField
label="密码"
required
error={touched.password && errors.password}
>
<input
type="password"
value={formData.password}
onChange={handleChange('password')}
onBlur={handleBlur('password')}
placeholder="至少 8 个字符"
autoComplete="new-password"
/>
</FormField>
<label className="checkbox-label">
<input
type="checkbox"
checked={formData.agreeTerms}
onChange={handleChange('agreeTerms')}
/>
我已阅读并同意《用户协议》
</label>
<button type="submit" disabled={!formData.agreeTerms}>
注册
</button>
</form>
);
};
🎯 非受控组件(Uncontrolled Components)适用场景
// 适用场景:文件上传、一次性表单、集成第三方库
const FileUploadForm = () => {
const fileInputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
// 通过 ref 访问 DOM 元素获取值(不需要 state)
const file = fileInputRef.current?.files[0];
if (file) {
uploadFile(file);
}
};
return (
<form onSubmit={handleSubmit}>
{/* 非受控:不绑定 value/state */}
<input
ref={fileInputRef}
type="file"
accept=".pdf,.doc,.docx"
/>
<button type="submit">上传文件</button>
</form>
);
};
2.3 组件生命周期管理
2.3.1 类组件生命周期(了解即可,用于阅读旧代码)
挂载阶段 (Mounting):
constructor() → static getDerivedStateFromProps() → render() → componentDidMount()
更新阶段 (Updating):
static getDerivedStateFromProps() → shouldComponentUpdate() → render() → getSnapshotBeforeUpdate() → componentDidUpdate()
卸载阶段 (Unmounting):
componentWillUnmount()
错误处理 (Error Handling):
static getDerivedStateFromError() → componentDidCatch()
📖 各阶段典型用途
class LegacyComponent extends React.Component {
constructor(props) {
super(props);
// 1. 初始化 state
// 2. 绑定方法 this
this.state = { data: null, loading: false };
}
// 3. 从 props 派生 state(少用,容易导致冗余 state)
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.userId !== prevState.userId) {
return { data: null }; // 重置数据
}
return null; // 不更新 state
}
// 4. 性能优化:决定是否重新渲染(慎用!)
shouldComponentUpdate(nextProps, nextState) {
// 仅当特定 props/state 变化时才更新
return nextProps.id !== this.props.id ||
nextState.loading !== this.state.loading;
}
// 5. DOM 挂载完成后:副作用操作(网络请求、订阅、DOM 操作)
componentDidMount() {
this.fetchData();
this.timer = setInterval(this.updateTime, 1000);
document.addEventListener('resize', this.handleResize);
}
// 6. DOM 更新前:获取快照(用于滚动位置恢复等)
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop; // 返回滚动偏移
}
return null;
}
// 7. DOM 更新完成后:DOM 操作、网络请求(基于 props 变化)
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
this.listRef.current.scrollTop =
this.listRef.current.scrollHeight - snapshot;
}
}
// 8. 组件卸载前:清理工作(取消订阅、清除定时器)
componentWillUnmount() {
clearInterval(this.timer);
document.removeEventListener('resize', this.handleResize);
this.abortController?.abort(); // 取消进行中的请求
}
// 9. 错误边界:捕获子组件渲染错误
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
logErrorToService(error, info.componentStack); // 上报错误监控
}
render() {
if (this.state.hasError) {
return <ErrorFallback />;
}
return <div ref={this.listRef}>{/* JSX */}</div>;
}
}
2.3.2 函数组件的生命周期替代方案(Hooks)
【现代标准】useEffect 覆盖 90% 的生命周期需求
// ✅ 函数组件等效实现
const ModernComponent = ({ userId }) => {
const [data, setData] = useState(null);
const [hasError, setHasError] = useState(false);
const listRef = useRef(null);
const abortRef = useRef(null);
// 等价于 componentDidMount + componentDidUpdate
useEffect(() => {
const controller = new AbortController();
abortRef.current = controller;
const fetchData = async () => {
try {
const response = await fetch(`/api/users/${userId}`, {
signal: controller.signal,
});
const result = await response.json();
setData(result);
} catch (err) {
if (err.name !== 'AbortError') {
setHasError(true);
}
}
};
fetchData();
// 等价于 componentWillUnmount
return () => {
controller.abort(); // 清理:取消请求
};
}, [userId]); // 依赖数组决定何时执行
// 等价于 componentDidMount(仅执行一次)
useEffect(() => {
const timer = setInterval(updateTime, 1000);
const handleResize = debounce(handleWindowResize, 200);
window.addEventListener('resize', handleResize);
return () => {
clearInterval(timer);
window.removeEventListener('resize', handleResize);
};
}, []); // 空依赖 = 仅挂载/卸载时执行
// 错误边界(需要单独的类组件或 react-error-boundary 库)
if (hasError) {
throw new Error('Component error'); // 被最近的 ErrorBoundary 捕获
}
return <div ref={listRef}>{/* 渲染内容 */}</div>;
};
2.4 Hooks 核心用法与进阶
2.4.1 useState —— 状态管理基础
【核心原则】State 是不可变的(Immutable),每次更新都是替换
🎯 基础用法
// 1. 基本用法
const Counter = () => {
const [count, setCount] = useState(0); // 初始值为 0
return (
<div>
<p>当前计数:{count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<button onClick={() => setCount(count - 1)}>减少</button>
<button onClick={() => setCount(0)}>重置</button>
</div>
);
};
// 2. 惰性初始化(初始值为计算密集型时使用)
const ExpensiveInit = ({ initialValue }) => {
// ❌ 每次渲染都会执行
// const [data, setData] = useState(computeExpensiveValue(initialValue));
// ✅ 仅首次渲染执行
const [data, setData] = useState(() => computeExpensiveValue(initialValue));
return <div>{data}</div>;
};
// 3. 函数式更新(解决闭包陷阱)
const Incrementer = () => {
const [count, setCount] = useState(0);
const incrementMultipleTimes = () => {
// ❌ 可能只增加 1(闭包问题)
// setCount(count + 1);
// setCount(count + 1);
// setCount(count + 1);
// ✅ 函数式更新:确保基于最新 state
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={incrementMultipleTimes}>+3</button>
</div>
);
};
// 4. 对象/数组的 state 更新(不可变更新模式)
const Form = () => {
const [form, setForm] = useState({
username: '',
email: '',
preferences: {
newsletter: false,
notifications: true,
},
});
// 更新单个字段
const updateUsername = (value) => {
setForm(prev => ({
...prev,
username: value, // 展开运算符合并
}));
};
// 更新嵌套字段
const toggleNewsletter = () => {
setForm(prev => ({
...prev,
preferences: {
...prev.preferences,
newsletter: !prev.preferences.newsletter,
},
}));
};
// 数组操作
const [items, setItems] = useState([]);
const addItem = (item) => setItems(prev => [...prev, item]);
const removeItem = (index) => setItems(prev => prev.filter((_, i) => i !== index));
const updateItem = (index, newItem) =>
setItems(prev => prev.map((item, i) => i === index ? newItem : item));
};
⚠️ 常见错误
// ❌ 错误 1:直接修改 state(违反不可变原则)
const BadMutation = () => {
const [list, setList] = useState([1, 2, 3]);
const addItemWrong = () => {
list.push(4); // 直接修改!React 无法检测变化
setList(list); // 不会触发重新渲染
};
// ✅ 正确:创建新数组
const addItemCorrect = () => {
setList([...list, 4]); // 创建新引用
};
};
// ❌ 错误 2:在渲染中直接调用 setState(无限循环)
const InfiniteLoop = () => {
const [count, setCount] = useState(0);
setCount(count + 1); // 每次渲染都触发更新 → 无限循环!
return <div>{count}</div>; // 永远不会执行到这里
};
// ✅ 正确:在事件处理或 useEffect 中调用
const CorrectUsage = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(c => c + 1); // 在副作用中更新
}, 1000);
return () => clearInterval(id);
}, []);
return <div>{count}</div>;
};
2.4.2 useEffect —— 副作用处理核心
【难点重点】useEffect 是新手最容易出错的地方
🎯 useEffect 执行时机图解
渲染 (render) → 提交到 DOM → 浏览器绘制 → useEffect 执行
↓
[浏览器可能在此处绘制]
📝 三种典型使用模式
// 模式 1:无依赖(每次渲染后都执行)—— 极少使用
const LogEveryRender = ({ prop }) => {
useEffect(() => {
console.log('组件渲染了,prop:', prop); // 每次渲染都执行
}); // 注意:不要遗漏依赖数组参数
return <div>{prop}</div>;
};
// 模式 2:空依赖(仅挂载和卸载时执行)—— 最常用
const SubscribeToService = ({ userId }) => {
useEffect(() => {
// componentDidMount
const subscription = userService.subscribe(userId, handleUpdate);
// componentWillUnmount
return () => {
subscription.unsubscribe(); // 清理函数
};
}, []); // 空数组 = 仅执行一次
return <div>User: {userId}</div>;
};
// 模式 3:特定依赖(依赖变化时执行)—— 数据同步场景
const SyncWithProps = ({ userId }) => {
const [userData, setUserData] = useState(null);
useEffect(() => {
let isMounted = true; // 防止卸载后 setState
const fetchUserData = async () => {
const data = await api.getUser(userId);
if (isMounted) { // 组件仍挂载才更新
setUserData(data);
}
};
fetchUserData();
return () => {
isMounted = false; // 清理:标记为已卸载
};
}, [userId]); // userId 变化时重新执行
if (!userData) return <Loading />;
return <UserProfile {...userData} />;
};
⚠️ 依赖数组规则(必须严格遵守)
// ❌ 错误 1:依赖数组遗漏变量(eslint 会警告)
const MissingDependency = ({ userId }) => {
const [filter, setFilter] = useState('all');
useEffect(() => {
// 使用了 filter 但未加入依赖数组
fetch(`/api/users/${userId}?filter=${filter}`);
}, [userId]); // 缺少 filter!
// ✅ 修复:添加所有外部变量到依赖数组
useEffect(() => {
fetch(`/api/users/${userId}?filter=${filter}`);
}, [userId, filter]); // 完整的依赖
};
// ❌ 错误 2:依赖数组包含过多变量(频繁触发 effect)
const TooManyDependencies = ({ a, b, c, d, e, f }) => {
useEffect(() => {
doSomething(a, b, c, d, e, f);
}, [a, b, c, d, e, f]); // 任意一个变化都会触发
// ✅ 优化:聚合相关状态或使用 useRef
const depsRef = useRef({ a, b, c, d, e, f });
useEffect(() => {
const { a, b, c, d, e, f } = depsRef.current;
doSomething(a, b, c, d, e, f);
}, [/* 手动控制依赖 */]);
};
// ✅ 最佳实践:使用 useMemo/useCallback 优化依赖
const OptimizedDependencies = ({ items, onSelect }) => {
// 稳定化的回调函数
const handleSelect = useCallback((id) => {
onSelect(id);
}, [onSelect]);
// 计算属性(避免不必要的 effect 触发)
const processedItems = useMemo(() => {
return items.map(item => ({ ...item, processed: true }));
}, [items]);
useEffect(() => {
// 现在 effect 只会在必要时触发
setupListener(processedItems, handleSelect);
}, [processedItems, handleSelect]);
};
2.4.3 useRef —— 引用与持久化
【用途】保存不触发渲染的可变值、访问 DOM 元素
🎯 三大典型场景
// 场景 1:访问 DOM 元素(最常见)
const AutoFocusInput = () => {
const inputRef = useRef(null);
useEffect(() => {
// DOM 挂载后自动聚焦
inputRef.current?.focus();
}, []);
return (
<div>
<input ref={inputRef} placeholder="自动聚焦的输入框" />
<button onClick={() => inputRef.current?.focus()}>
点击聚焦
</button>
</div>
);
};
// 场景 2:保存上次渲染的值(不触发重渲染)
const PreviousValue = ({ value }) => {
const prevValue = useRef(value);
useEffect(() => {
// 更新 ref(不影响渲染)
prevValue.current = value;
}, [value]);
// 显示变化信息
const changed = prevValue.current !== value;
return (
<div style={{ color: changed ? 'red' : 'green' }}>
当前值:{value} {changed && `(从 ${prevValue.current} 变化)`}
</div>
);
};
// 场景 3:存储定时器 ID、WebSocket 实例等(清理必需)
const TimerComponent = () => {
const [seconds, setSeconds] = useState(0);
const intervalRef = useRef(null);
const startTimer = () => {
if (intervalRef.current) return; // 防止重复启动
intervalRef.current = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
};
const stopTimer = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
// 组件卸载时自动清理
useEffect(() => {
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, []);
return (
<div>
<p>计时:{seconds}秒</p>
<button onClick={startTimer}>开始</button>
<button onClick={stopTimer}>停止</button>
</div>
);
};
⚠️ useRef vs useState 选择指南
| 场景 | 使用 useRef | 使用 useState |
|---|---|---|
| 需要触发 UI 更新 | ❌ | ✅ |
| 访问 DOM 元素 | ✅ | ❌ |
| 保存定时器/订阅引用 | ✅ | ❌ |
| 存储上一轮的值 | ✅ | ❌ |
| 存储计算结果(需要渲染) | ❌ | ✅ |
2.4.4 useContext —— 跨组件状态共享
【适用场景】主题、语言、用户信息等全局状态
🎯 基础用法
// 1. 创建 Context
const ThemeContext = createContext({
theme: 'light',
toggleTheme: () => {},
});
// 2. 提供者组件(通常在应用顶层)
const App = () => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
};
const contextValue = useMemo(() => ({
theme,
toggleTheme,
}), [theme]);
return (
<ThemeContext.Provider value={contextValue}>
<Header />
<MainContent />
<Footer />
</ThemeContext.Provider>
);
};
// 3. 消费者组件(任意层级)
const ThemedButton = () => {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
className={`btn btn--${theme}`}
>
切换为{theme === 'light' ? '深色' : '浅色'}模式
</button>
);
};
// 4. 也可以在 Class 组件中使用(render props 模式)
class LegacyButton extends React.Component {
render() {
return (
<ThemeContext.Consumer>
{({ theme }) => (
<button className={`btn btn--${theme}`}>
类组件按钮
</button>
)}
</ThemeContext.Consumer>
);
}
}
⚠️ 性能陷阱与优化
// ❌ 性能问题:Context 值变化导致所有消费者重渲染
const BadProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
// 每次渲染都创建新对象 → 所有消费者都会重渲染
return (
<AppContext.Provider value={{
user,
theme,
setUser,
setTheme, // 函数引用每次都变!
}}>
{children}
</AppContext.Provider>
);
};
// ✅ 优化方案 1:拆分多个 Context(最小化重渲染范围)
const UserContext = createContext(null);
const ThemeContext = createContext(null);
const OptimizedProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
return (
<UserContext.Provider value={user}>
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
</UserContext.Provider>
);
};
// ✅ 优化方案 2:使用 useMemo 稳定化值
const StableProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const memoizedValue = useMemo(() => ({
user,
setUser, // setState 本身是稳定的
theme,
setTheme: useCallback(() => { // 包装成稳定引用
setTheme(t => t === 'light' ? 'dark' : 'light');
}, []),
}), [user, theme]);
return (
<AppContext.Provider value={memoizedValue}>
{children}
</AppContext.Provider>
);
};
2.5 组件通信架构设计
2.5.1 父子组件通信(Props Down, Events Up)
【单向数据流】React 的核心原则
// 父组件:状态提升(Lifting State Up)
const TodoApp = () => {
const [todos, setTodos] = useState([
{ id: 1, text: '学习 React', completed: false },
{ id: 2, text: '完成作业', completed: true },
]);
// 定义操作函数(向下传递给子组件)
const addTodo = (text) => {
const newTodo = {
id: Date.now(), // 生产环境应使用 UUID
text,
completed: false,
};
setTodos(prev => [...prev, newTodo]);
};
const toggleTodo = (id) => {
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
};
const deleteTodo = (id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
};
return (
<div className="todo-app">
<h1>待办事项</h1>
{/* 向下传递数据和操作函数 */}
<TodoForm onAdd={addTodo} />
<TodoList
todos={todos}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
<TodoStats todos={todos} />
</div>
);
};
// 子组件 1:表单(接收回调函数)
const TodoForm = ({ onAdd }) => {
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!text.trim()) return;
onAdd(text.trim()); // 调用父组件传递的函数
setText('');
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="添加新的待办事项..."
/>
<button type="submit">添加</button>
</form>
);
};
// 子组件 2:列表(展示数据 + 触发事件)
const TodoList = ({ todos, onToggle, onDelete }) => {
if (todos.length === 0) {
return <EmptyState message="暂无待办事项" />;
}
return (
<ul className="todo-list">
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={() => onToggle(todo.id)}
onDelete={() => onDelete(todo.id)}
/>
))}
</ul>
);
};
2.5.2 兄弟组件通信(状态提升 + Context)
【架构原则】共享状态应该提升到最近的共同祖先组件
方案 1:状态提升(适用于简单场景)
// 共同父组件:管理共享状态
const ProductPage = () => {
const [selectedCategoryId, setSelectedCategoryId] = useState(null);
return (
<div className="product-page">
<aside>
{/* 兄弟组件 1:选择分类 */}
<CategoryList
selectedId={selectedCategoryId}
onSelect={setSelectedCategoryId}
/>
</aside>
<main>
{/* 兄弟组件 2:根据分类显示产品 */}
<ProductGrid categoryId={selectedCategoryId} />
</main>
</div>
);
};
方案 2:Context(适用于深层嵌套或多层兄弟通信)
// 创建共享 Context
const SelectedCategoryContext = createContext(null);
// 顶层提供数据
const App = () => {
const [selectedCategory, setSelectedCategory] = useState(null);
return (
<SelectedCategoryContext.Provider value={{
selectedCategory,
setSelectedCategory,
}}>
<ProductPage />
</SelectedCategoryContext.Provider>
);
};
// 任意层级的兄弟组件都可以消费
const CategoryList = () => {
const { selectedCategory, setSelectedCategory } = useContext(SelectedCategoryContext);
// ... 使用共享状态
};
const ProductGrid = () => {
const { selectedCategory } = useContext(SelectedCategoryContext);
// ... 使用共享状态
};
2.5.3 跨层级通信(Context / Event Bus / 状态管理库)
【选型依据】根据项目规模和复杂度选择合适方案
| 方案 | 适用场景 | 复杂度 | 性能 | 可扩展性 |
|---|---|---|---|---|
| Props Drilling | 1-2 层传递 | 低 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| Context API | 全局状态(主题/语言/用户) | 中 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| useReducer + Context | 复杂全局状态 | 中高 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Redux/Zustand | 大型应用、多人协作 | 高 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Event Bus | 松耦合组件通知 | 中 | ⭐⭐⭐⭐ | ⭐⭐ |
🎯 实战:构建可扩展的状态管理层
// store/auth-context.js
const AuthContext = createContext(null);
const initialState = {
user: null,
token: null,
isAuthenticated: false,
isLoading: false,
error: null,
};
function authReducer(state, action) {
switch (action.type) {
case 'LOGIN_START':
return { ...state, isLoading: true, error: null };
case 'LOGIN_SUCCESS':
return {
...state,
isLoading: false,
isAuthenticated: true,
user: action.payload.user,
token: action.payload.token,
};
case 'LOGIN_FAILURE':
return {
...state,
isLoading: false,
error: action.payload.error,
isAuthenticated: false,
};
case 'LOGOUT':
return { ...initialState };
default:
return state;
}
}
export function AuthProvider({ children }) {
const [state, dispatch] = useReducer(authReducer, initialState);
// 使用 useMemo 避免不必要的重渲染
const contextValue = useMemo(() => ({
...state,
login: async (credentials) => {
dispatch({ type: 'LOGIN_START' });
try {
const result = await authService.login(credentials);
dispatch({ type: 'LOGIN_SUCCESS', payload: result });
} catch (error) {
dispatch({ type: 'LOGIN_FAILURE', payload: { error: error.message } });
}
},
logout: () => {
dispatch({ type: 'LOGOUT' });
authService.clearToken();
},
}), [state]);
return (
<AuthContext.Provider value={contextValue}>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
};
三、基础生态工具
3.1 路由管理:React Router v6
3.1.1 核心概念与配置
【架构要点】路由即组件,声明式配置优于命令式导航
// router/index.jsx
import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom';
// 路由配置(集中管理,便于维护)
const router = createBrowserRouter([
{
path: '/',
element: <Layout />,
errorElement: <ErrorBoundary />, // 全局错误处理
children: [
{
index: true, // 默认子路由(等同于 path: '')
element: <HomePage />,
},
{
path: 'about',
element: <AboutPage />,
},
{
path: 'products',
element: <ProductsLayout />, // 嵌套路由的布局组件
children: [
{
index: true,
element: <ProductList />,
},
{
path: ':productId', // 动态参数
element: <ProductDetail />,
loader: productLoader, // 数据预加载
},
],
},
{
path: 'dashboard',
element: (
<ProtectedRoute> {/* 路由守卫 */}
<DashboardPage />
</ProtectedRoute>
),
children: [
{ path: 'profile', element: <ProfilePage /> },
{ path: 'settings', element: <SettingsPage /> },
],
},
{
path: '*',
element: <NotFoundPage />, // 404 页面
},
],
},
]);
// App 入口
const App = () => {
return <RouterProvider router={router} />;
};
3.1.2 路由导航与传参
【三种传参方式】动态参数 / searchParams / state
// 1. 动态参数(URL 路径的一部分)
const ProductDetail = () => {
const { productId } = useParams(); // 获取 :productId
const location = useLocation();
const product = useLoaderData(); // 使用 loader 预加载的数据
return (
<div>
<h1>产品详情 #{productId}</h1>
<p>完整路径:{location.pathname}</p>
<ProductInfo product={product} />
</div>
);
};
// 2. Search Params(查询参数)
const SearchResults = () => {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('q');
const page = parseInt(searchParams.get('page') || '1');
const handleSearch = (newQuery) => {
setSearchParams({ q: newQuery, page: '1' }); // 更新查询参数
};
return (
<div>
<SearchBar
defaultValue={query}
onSearch={handleSearch}
/>
<ResultList query={query} page={page} />
<Pagination
currentPage={page}
onPageChange={(newPage) =>
setSearchParams({ q: query, page: String(newPage) })
}
/>
</div>
);
};
// 3. 导航状态(不显示在 URL 中,页面刷新后丢失)
const UserList = () => {
const navigate = useNavigate();
const handleUserClick = (user) => {
navigate(`/users/${user.id}`, {
state: { from: 'list', timestamp: Date.now() }, // 传递隐藏状态
});
};
return (
<table>
{users.map(user => (
<tr key={user.id} onClick={() => handleUserClick(user)}>
<td>{user.name}</td>
</tr>
))}
</table>
);
};
// 接收端读取 state
const UserDetail = () => {
const { id } = useParams();
const location = useLocation();
const navigationState = location.state; // { from: 'list', timestamp: ... }
useEffect(() => {
if (navigationState?.from === 'list') {
analytics.track('view_from_list', { userId: id });
}
}, [id, navigationState]);
};
3.1.3 路由守卫与权限控制
【安全关键】前端路由守卫只是 UX 优化,真正的权限验证必须在后端
// components/ProtectedRoute.jsx
const ProtectedRoute = ({ children, requiredRoles = [] }) => {
const { isAuthenticated, user, isLoading } = useAuth();
const location = useLocation();
// 加载中状态
if (isLoading) {
return <FullPageLoading />;
}
// 未登录:重定向到登录页,记录来源地址
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
// 角色权限检查
if (requiredRoles.length > 0 && !requiredRoles.includes(user.role)) {
return <Navigate to="/unauthorized" replace />;
}
return children;
};
// 使用示例
const router = createBrowserRouter([
{
path: '/admin',
element: (
<ProtectedRoute requiredRoles={['admin', 'super_admin']}>
<AdminPanel />
</ProtectedRoute>
),
},
{
path: '/profile',
element: (
<ProtectedRoute>
<ProfilePage />
</ProtectedRoute>
),
},
]);
3.2 状态管理方案选型
3.2.1 原生 Context + useReducer(中小型项目首选)
【优势】零依赖、React 内置、足够应对大多数场景
🎯 完整示例:购物车状态管理
// store/cart-context.js
const CartContext = createContext(null);
const CART_ACTIONS = {
ADD_ITEM: 'ADD_ITEM',
REMOVE_ITEM: 'REMOVE_ITEM',
UPDATE_QUANTITY: 'UPDATE_QUANTITY',
CLEAR_CART: 'CLEAR_CART',
APPLY_COUPON: 'APPLY_COUPON',
};
const initialState = {
items: [], // [{ id, name, price, quantity, image }]
couponCode: null,
discount: 0,
subtotal: 0,
total: 0,
};
function cartReducer(state, action) {
switch (action.type) {
case CART_ACTIONS.ADD_ITEM: {
const existingIndex = state.items.findIndex(
item => item.id === action.payload.id
);
if (existingIndex >= 0) {
// 商品已存在,增加数量
const updatedItems = [...state.items];
updatedItems[existingIndex] = {
...updatedItems[existingIndex],
quantity: updatedItems[existingIndex].quantity + 1,
};
return calculateTotals({ ...state, items: updatedItems });
}
// 新商品
const newItems = [...state.items, { ...action.payload, quantity: 1 }];
return calculateTotals({ ...state, items: newItems });
}
case CART_ACTIONS.REMOVE_ITEM: {
const filteredItems = state.items.filter(
item => item.id !== action.payload
);
return calculateTotals({ ...state, items: filteredItems });
}
case CART_ACTIONS.UPDATE_QUANTITY: {
const { itemId, quantity } = action.payload;
if (quantity <= 0) {
return cartReducer(state, { type: CART_ACTIONS.REMOVE_ITEM, payload: itemId });
}
const updatedItems = state.items.map(item =>
item.id === itemId ? { ...item, quantity } : item
);
return calculateTotals({ ...state, items: updatedItems });
}
case CART_ACTIONS.CLEAR_CART:
return initialState;
case CART_ACTIONS.APPLY_COUPON: {
const discount = calculateDiscount(state.subtotal, action.payload);
return { ...state, couponCode: action.payload, discount };
}
default:
return state;
}
}
// 辅助函数:计算总价
function calculateTotals(state) {
const subtotal = state.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
const total = subtotal - state.discount;
return { ...state, subtotal, total };
}
export function CartProvider({ children }) {
const [cart, dispatch] = useReducer(cartReducer, initialState);
const contextValue = useMemo(() => ({
...cart,
addItem: (product) => dispatch({ type: CART_ACTIONS.ADD_ITEM, payload: product }),
removeItem: (itemId) => dispatch({ type: CART_ACTIONS.REMOVE_ITEM, payload: itemId }),
updateQuantity: (itemId, quantity) =>
dispatch({ type: CART_ACTIONS.UPDATE_QUANTITY, payload: { itemId, quantity } }),
clearCart: () => dispatch({ type: CART_ACTIONS.CLEAR_CART }),
applyCoupon: (code) => dispatch({ type: CART_ACTIONS.APPLY_COUPON, payload: code }),
}), [cart]);
return (
<CartContext.Provider value={contextValue}>
{children}
</CartContext.Provider>
);
}
export const useCart = () => {
const context = useContext(CartContext);
if (!context) {
throw new Error('useCart must be used within CartProvider');
}
return context;
};
3.2.2 Redux Toolkit(大型项目标准)
【何时升级到 Redux】 当出现以下信号时考虑引入:
- 多个不相关的组件需要访问相同状态
- 状态更新逻辑复杂,需要中间件(日志、持久化、异步)
- 需要时间旅行调试(DevTools)
- 团队规模较大,需要统一的状态管理模式
// store/index.js (Redux Toolkit)
import { configureStore, createSlice } from '@reduxjs/toolkit';
// 1. 创建 Slice( reducer + actions)
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
status: 'idle',
},
reducers: {
increment: (state) => {
state.value += 1; // Immer 允许直接修改(内部转换为不可变更新)
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
// 异步 thunk
extraReducers: (builder) => {
builder
.addCase(incrementAsync.pending, (state) => {
state.status = 'loading';
})
.addCase(incrementAsync.fulfilled, (state, action) => {
state.status = 'idle';
state.value += action.payload;
});
},
});
// 2. 异步 Action Creator
export const incrementAsync = (amount) => async (dispatch) => {
dispatch(counterSlice.actions.increment());
// 模拟 API 调用
const result = await api.fetchIncrement(amount);
dispatch(incrementByAmount(result.value));
};
// 3. 配置 Store
export const store = configureStore({
reducer: {
counter: counterSlice.reducer,
// 其他 slice...
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false, // 根据需要配置
}),
});
// App.js
import { Provider } from 'react-redux';
import { store } from './store';
const App = () => (
<Provider store={store}>
<RouterProvider router={router} />
</Provider>
);
// 组件中使用
const CounterComponent = () => {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
</div>
);
};
3.3 工程化配置与构建优化
3.3.1 项目初始化:Vite vs CRA(Create React App)
【2026年推荐】Vite 是事实标准,CRA 已进入维护模式
| 维度 | Vite (推荐) | CRA (Legacy) |
|---|---|---|
| 启动速度 | ⚡ 毫秒级(原生 ESM) | 🐢 10-30秒(Webpack 打包) |
| 热更新速度 | ⚡ 即时更新 | 🐢 2-10秒 |
| 配置灵活性 | ✅ 高度可定制 | ⚠️ eject 后才能定制 |
| 生态成熟度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| TypeScript 支持 | ✅ 原生支持 | ✅ 支持 |
| 官方维护状态 | 🟢 活跃开发 | 🟡 维护模式(不再添加新特性) |
🚀 Vite 项目初始化与配置
# 创建项目
npm create vite@latest my-react-app -- --template react-ts
cd my-react-app
npm install
# 安装常用依赖
npm install react-router-dom axios
npm install -D @types/node tailwindcss postcss autoprefixer
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'), // 路径别名
},
},
server: {
port: 3000,
open: true,
proxy: {
'/api': {
target: 'http://localhost:8080', // 后端服务地址
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
build: {
outDir: 'dist',
sourcemap: false, // 生产环境关闭 sourcemap(安全考虑)
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'], // 第三方库分包
router: ['react-router-dom'],
},
},
},
},
});
3.3.2 包管理器选择
【团队一致性】选择一种并在整个项目中统一使用
| 包管理器 | 锁文件 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| npm | package-lock.json | Node.js 官方、兼容性好 | 速度较慢 | 传统项目、CI/CD |
| yarn | yarn.lock | 速度快、离线模式 | 有时版本冲突 | 大型 monorepo |
| pnpm | pnpm-lock.yaml | 节省磁盘空间、严格依赖 | 生态相对较新 | 现代项目(强烈推荐) |
📦 pnpm 配置示例
# 安装 pnpm
npm install -g pnpm
# 项目初始化
pnpm create vite my-app --template react
cd my-app
pnpm install
# 常用命令
pnpm add axios # 安装依赖
pnpm add -D @types/react # 安装开发依赖
pnpm remove lodash # 移除依赖
pnpm update # 更新依赖
pnpm audit # 安全审计
// .npmrc(项目级别配置)
shamefully-hoist=false
strict-peer-dependencies=false
save-exact=true # 锁定精确版本
3.3.3 ESM 模块化(ECMAScript Modules)
【现代标准】ESM 是 JavaScript 的官方模块系统,取代 CommonJS
// 导出(Named Exports)
// utils/helpers.js
export const formatDate = (date) => { /* ... */ };
export const formatCurrency = (amount) => { /* ... */ };
export const debounce = (fn, delay) => { /* ... */ };
// 默认导出(Default Export)
// components/Button.jsx
export default function Button({ children, onClick }) {
return <button onClick={onClick}>{children}</button>;
}
// 导入使用
import Button, { formatDate, formatCurrency } from '@/components/Button';
import { debounce } from '@/utils/helpers';
// 动态导入(Code Splitting)
const HeavyComponent = lazy(() => import('./HeavyComponent'));
// 动态导入(按需加载)
const loadModule = async () => {
const module = await import(`./modules/${moduleName}`);
module.init();
};
3.4 UI 框架集成规范
3.4.1 Ant Design(企业级后台首选)
【集成要点】按需引入、主题定制、国际化
// 1. 安装
npm install antd @ant-design/icons
// 2. 按需引入(Vite + unplugin-auto-import)
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite';
export default defineConfig({
plugins: [
AutoImport({
resolvers: [
(name) => {
if (name.startsWith('A')) {
const component = name.slice(1);
return { importName: component, from: 'antd' };
}
},
],
}),
],
});
// 3. 主题定制(Design Token)
// App.jsx 或入口文件
import { ConfigProvider, theme } from 'antd';
const App = () => (
<ConfigProvider
theme={{
algorithm: theme.darkAlgorithm, // 暗色主题
token: {
colorPrimary: '#1890ff', // 主色调
borderRadius: 8,
},
components: {
Button: {
primaryShadow: 'none', // 移除按钮阴影
},
},
}}
locale={zhCN} // 国际化
>
<RouterProvider router={router} />
</ConfigProvider>
);
// 4. 业务组件封装(统一规范)
const ProTable = ({
columns,
dataSource,
loading,
pagination,
onPaginationChange,
...restProps
}) => {
return (
<Table
columns={columns}
dataSource={dataSource}
loading={loading}
pagination={{
current: pagination.page,
pageSize: pagination.pageSize,
total: pagination.total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `共 ${total} 条`,
onChange: (page, pageSize) => {
onPaginationChange?.({ page, pageSize });
},
}}
rowKey="id"
size="middle"
{...restProps}
/>
);
};
3.4.2 UI 框架选型对比
| 框架 | 适用场景 | 定制难度 | Bundle Size | 社区活跃度 |
|---|---|---|---|---|
| Ant Design | 企业后台、B端产品 | ⭐⭐ | 较大 | ⭐⭐⭐⭐⭐ |
| Material UI | 产品导向、Material Design | ⭐⭐⭐ | 较大 | ⭐⭐⭐⭐⭐ |
| Arco Design | 字节系、现代化界面 | ⭐⭐ | 中等 | ⭐⭐⭐⭐ |
| Headless UI | 高度定制需求 | ⭐⭐⭐⭐⭐ | 极小 | ⭐⭐⭐ |
| Radix UI | 无障碍优先 | ⭐⭐⭐⭐ | 小 | ⭐⭐⭐⭐ |
🎯 UI 组件二次封装原则
// 1. 统一接口规范
interface TableProps<T> {
columns: ColumnType<T>[];
dataSource: T[];
loading?: boolean;
pagination?: PaginationConfig;
rowSelection?: TableRowSelection<T>;
onRowClick?: (record: T) => void;
emptyText?: React.ReactNode;
}
// 2. 统一错误处理
const AsyncButton = ({ onClick, ...props }) => {
const [loading, setLoading] = useState(false);
const handleClick = async (e) => {
setLoading(true);
try {
await onClick(e);
} finally {
setLoading(false);
}
};
return <Button loading={loading} onClick={handleClick} {...props} />;
};
// 3. 统一数据格式转换
const transformApiDataToTableColumns = (fields) => {
return fields.map(field => ({
title: field.label,
dataIndex: field.key,
key: field.key,
width: field.width,
fixed: field.fixed,
render: field.render || ((value) => value ?? '-'), // 空值显示
}));
};
四、调试与代码规范
4.1 React DevTools 高级调试
4.1.1 组件面板深度使用
【核心功能】不仅仅是查看 Props 和 State
🔍 主要功能区域
┌─────────────────────────────────────────────┐
│ Components 面板 │
├─────────────────────────────────────────────┤
│ ┌─ <App> │
│ │ ├─ <Router> │
│ │ │ ├─ <Routes> │
│ │ │ │ ├─ <Layout> │
│ │ │ │ │ ├─ <Header> ✓ 已挂载 │
│ │ │ │ │ ├─ <Sidebar> │
│ │ │ │ │ └─ <MainContent> │
│ │ │ │ │ ├─ <UserList> │
│ │ │ │ │ │ ├─ <UserCard> × 10 │
│ │ │ │ │ │ └─ <UserCard> ⚡ 选中 │
│ │ │ │ │ └─ <Pagination> │
└─────────────────────────────────────────────┘
🎯 高级调试技巧
// 1. 在控制台访问选中组件实例
// 选中组件后,在 Console 输入:
$r.props // 查看 Props
$r.state // 查看 State(类组件)
$r.hooks // 查看 Hooks 状态
$r.forceUpdate() // 强制重新渲染(调试用)
// 2. 追踪组件渲染原因(React 18+)
// 在组件中添加:
const whyDidYouRender = require('@welldone-software/why-did-you-render');
if (process.env.NODE_ENV === 'development') {
whyDidYouRender(React, {
trackAllPureComponents: true,
});
}
// 3. 使用 React Developer Tools Profiler 录制性能
// - 点击录制按钮
// - 执行操作(如点击、输入)
// - 停止录制,查看 Flame Chart
// - 识别渲染耗时的组件(红色标记)
4.1.2 Profiler 性能分析
【优化入口】识别性能瓶颈的第一步
Flame Chart(火焰图)示例:
<App> ████████████████████████ 120ms
<Router> ██████████████████ 100ms
<Layout> ██████████████ 80ms
<UserList> ██████████ 60ms ← 瓶颈组件
<UserCard> ████ 15ms × 20 ← 重复渲染
<UserCard> ████ 15ms × 20
<Sidebar> ██ 10ms
<Header> ██ 8ms
🎯 优化前后对比
// ❌ 优化前:每个 UserCard 都独立重渲染
const UserListBad = ({ users, onSelect }) => (
<div>
{users.map(user => (
<UserCard
key={user.id}
user={user}
onSelect={onSelect} // 每次都是新函数引用!
/>
))}
</div>
);
// ✅ 优化后:使用 React.memo + useCallback
const UserCardMemo = React.memo(function UserCard({ user, onSelect }) {
console.log(`Rendering UserCard: ${user.id}`); // 观察渲染次数
return (
<div onClick={() => onSelect(user.id)}>
{user.name}
</div>
);
});
const UserListGood = ({ users, onSelect }) => {
const handleSelect = useCallback((id) => {
onSelect(id);
}, [onSelect]); // 稳定化回调
return (
<div>
{users.map(user => (
<UserCardMemo
key={user.id}
user={user}
onSelect={handleSelect}
/>
))}
</div>
);
};
4.2 ESLint 与代码质量保障
4.2.1 ESLint 配置(React 项目标准配置)
【规则制定】严格的 Lint 规则是工程质量的第一道防线
// .eslintrc.cjs
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended', // Hooks 规则(必须!)
'plugin:@typescript-eslint/recommended',
'prettier', // 与 Prettiet 集成
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
plugins: ['react', 'react-hooks', '@typescript-eslint'],
rules: {
// React 相关规则
'react/react-in-jsx-scope': 'off', // React 17+ 不需要导入 React
'react/prop-types': 'off', // TypeScript 替代 PropTypes
'react/no-unescaped-entities': 'off',
// Hooks 规则(防止常见错误)
'react-hooks/rules-of-hooks': 'error', // Hooks 只能在组件顶层调用
'react-hooks/exhaustive-deps': 'warn', // useEffect 依赖完整性检查
// TypeScript 规则
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'warn', // 避免使用 any
// 代码质量规则
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': 'error',
eqeqeq: ['error', 'always'], // 强制使用 ===
'prefer-const': 'error',
'no-var': 'error',
},
settings: {
react: {
version: 'detect', // 自动检测 React 版本
},
},
};
4.2.2 Prettier 格式化配置
【风格统一】让团队成员不再争论代码格式
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "es5",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf",
"jsxSingleQuote": false,
"jsxBracketSameLine": false
}
// package.json scripts
{
"scripts": {
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
"lint:fix": "eslint 'src/**/*.{js,jsx,ts,tsx}' --fix",
"format": "prettier --write 'src/**/*.{js,jsx,ts,tsx,css,json}'",
"prepare": "husky install"
}
}
4.2.3 Git Hooks(提交前自动检查)
【自动化】使用 Husky + lint-staged 保证代码质量
# 安装依赖
npm install -D husky lint-staged
# 初始化 Husky
npx husky install
npx husky add .husky/pre-commit "npx lint-staged"
npx husky add .husky/commit-msg "npx commitlint --edit $1"
// package.json
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{css,scss,less}": [
"stylelint --fix",
"prettier --write"
],
"*.{json,md}": [
"prettier --write"
]
}
}
4.3 工程化规范体系
4.3.1 目录结构规范(分层架构)
【可维护性】清晰的目录结构是大型项目的基础
src/
├── assets/ # 静态资源(图片、字体、图标)
│ ├── images/
│ ├── fonts/
│ └── icons/
├── components/ # 通用组件(可复用)
│ ├── ui/ # 基础 UI 组件(Button, Input, Modal...)
│ │ ├── Button/
│ │ │ ├── Button.jsx
│ │ │ ├── Button.module.css
│ │ │ └── index.ts
│ │ └── Input/
│ └── business/ # 业务组件(与具体业务相关)
│ ├── UserCard/
│ └── ProductTable/
├── hooks/ # 自定义 Hooks
│ ├── useAuth.js
│ ├── useDebounce.js
│ └── useRequest.js
├── pages/ # 页面组件(路由级别)
│ ├── Home/
│ │ ├── index.jsx
│ │ ├── Home.module.css
│ │ └── components/ # 页面专属组件
│ ├── About/
│ └── Dashboard/
├── services/ # API 服务层
│ ├── api.js # Axios 实例
│ ├── auth.service.js # 认证相关 API
│ └── user.service.js # 用户相关 API
├── store/ # 状态管理
│ ├── context/ # Context 实现
│ └── slices/ # Redux Slices
├── utils/ # 工具函数
│ ├── helpers.js
│ ├── constants.js
│ └── validators.js
├── styles/ # 全局样式
│ ├── variables.css # CSS 变量
│ ├── global.css # 全局样式
│ └── mixins.css # 样式混入
├── types/ # TypeScript 类型定义
│ ├── index.d.ts
│ └── api.types.ts
├── router/ # 路由配置
│ └── index.tsx
├── App.jsx # 应用根组件
├── main.jsx # 应用入口
└── vite-env.d.ts # Vite 类型声明
4.3.2 命名规范(团队协作基础)
| 类型 | 命名规范 | 示例 |
|---|---|---|
| 组件文件 | PascalCase | UserProfile.jsx, DataTable.jsx |
| 工具函数 | camelCase | formatDate.js, calculateTotal.js |
| 常量 | UPPER_SNAKE_CASE | API_BASE_URL, MAX_RETRY_COUNT |
| CSS 类名 | kebab-case (BEM) | .user-card__title--active |
| TypeScript 接口 | PascalCase | interface IUserProps |
| 枚举 | PascalCase | enum UserRole |
| 事件处理 | handle + 动作 | handleSubmit, handleClick |
| 布尔值 | is/has/can/should | isLoading, isVisible, hasPermission |
4.3.3 Git Commit 规范(Conventional Commits)
【可追溯性】规范的提交信息便于生成 Changelog 和回滚
<type>(<scope>): <subject>
<body>
<footer>
📝 Type 类型说明
| Type | 描述 | 示例 |
|---|---|---|
| feat | 新功能 | feat(auth): add OAuth2 login support |
| fix | Bug 修复 | fix(cart): resolve quantity calculation error |
| docs | 文档变更 | docs(readme): update installation guide |
| style | 代码格式(不影响运行) | style(button): fix indentation |
| refactor | 重构(非新功能、非修复) | refactor(user): extract validation logic |
| perf | 性能优化 | perf(list): implement virtual scrolling |
| test | 测试相关 | test(login): add unit tests for form validation |
| chore | 构建/工具变动 | chore(deps): upgrade react to 18.2.0 |
🔧 配置 Commitlint
// commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'revert'],
],
'subject-max-length': [2, 'always', 72], // 主题不超过 72 字符
},
};
4.3.4 代码审查清单(Code Review Checklist)
【质量保障】PR 提交前的自检清单
✅ 功能性检查
- 代码实现了预期的功能吗?
- 是否处理了边界情况(空值、异常输入)?
- 是否有必要的单元测试?
- API 调用是否有错误处理?
✅ 代码质量检查
- 命名是否清晰表达意图?
- 函数/组件是否遵循单一职责原则?
- 是否消除了重复代码(DRY 原则)?
- 注释是否必要且准确?(好的代码不需要注释解释"做什么",而是"为什么")
✅ 性能检查
- 是否有不必要的重渲染?(使用 React.memo、useMemo、useCallback)
- 大列表是否使用了虚拟滚动?
- 图片/资源是否懒加载?
- Bundle size 是否过大?(分析 webpack/vite bundle report)
✅ 安全性检查
- 是否有 XSS 风险?(避免 dangerouslySetInnerHTML,或使用 DOMPurify)
- 敏感数据是否明文存储?(Token、密码等应加密)
- API 请求是否有 CSRF 防护?
- 用户输入是否经过校验和清洗?
✅ 可访问性(Accessibility)
- 图片是否有 alt 属性?
- 表单元素是否有关联的 label?
- 颜色是否不是唯一的区分方式?(考虑色盲用户)
- 键盘导航是否正常工作?
附录:学习路线图与资源推荐
📚 官方资源(必读)
- React 官方文档:https://react.dev (最新版,推荐从头到尾学习)
- React Router 文档:https://reactrouter.com (v6 最新特性)
- Redux Toolkit 文档:https://redux-toolkit.js.org (现代 Redux 用法)
🛠️ 推荐工具链
- 构建工具:Vite 5+
- 代码规范:ESLint 9+ + Prettier 3+
- Git Hooks:Husky 9+ + lint-staged
- 测试框架:Vitest + React Testing Library
- UI 框架:Ant Design 5+ / Arco Design / Headless UI
- 状态管理:Zustand / Redux Toolkit / Jotai
📖 进阶方向(完成基础后)
- 性能优化:React.memo、useMemo、虚拟列表、代码分割、Suspense
- TypeScript 深度集成:泛型、类型体操、类型保护
- 服务端渲染(SSR):Next.js / Remix
- 微前端:qiankun / Module Federation
- 测试驱动开发(TDD):单元测试、集成测试、E2E 测试
- CI/CD 流水线:GitHub Actions / Jenkins / GitLab CI
- 监控与告警:Sentry(错误监控)、Lighthouse(性能监控)
🎯 实战项目建议(循序渐进)
Level 1:Todo List(1-2 天)
- 目标:熟练使用 useState、useEffect、表单处理
- 要点:CRUD 操作、本地存储、条件渲染
Level 2:个人博客(3-5 天)
- 目标:掌握路由、API 请求、列表渲染
- 要点:文章列表/详情页、分类筛选、搜索功能、Markdown 渲染
Level 3:电商后台管理(1-2 周)
- 目标:综合运用所有基础知识
- 要点:表格 CRUD、表单校验、权限控制、状态管理、图表展示
Level 4:在线聊天室(2-3 周)
- 目标:实时数据处理、复杂状态管理
- 要点:WebSocket 连接、消息列表、在线状态、文件上传
总结
本文档从资深 React 架构师的视角,系统地梳理了 React 开发的核心知识体系。每个知识点不仅讲解了"是什么"和"怎么用",更重要的是从以下维度进行了深度剖析:
✅ 架构合理性:为什么这样设计?有什么替代方案?
✅ 工程质量:如何写出可维护、可测试的代码?
✅ 性能优化:哪里可能有性能瓶颈?如何优化?
✅ 可扩展性:当前方案能否支撑未来的需求增长?
✅ 安全性:是否存在安全隐患?如何防范?
核心理念:
- 基础扎实:JavaScript/CSS/浏览器原理是地基,不能跳过
- 理解原理:不只是会用 API,要理解背后的设计思想
- 最佳实践:参考业界标准(大厂规范),避免踩坑
- 持续进化:React 生态系统快速发展,保持学习热情
下一步行动建议:
- 通读本文档,标记不熟悉的知识点
- 对照官方文档深入学习
- 动手完成 Level 1-2 的实战项目
- 加入社区,参与开源项目
- 持续练习,形成肌肉记忆
版本:v1.0
最后更新:2026-05-10
适用范围:React 18+ / React Router v6 / Vite 5+
作者视角:基于 5 年+ React 大厂实战经验整理
更多推荐

所有评论(0)