React 完整知识体系与实战教程
目录
- [React 概述与发展历程](#React 概述与发展历程)
- 核心概念
- [JSX 深入](#JSX 深入)
- 组件系统
- [Props 与 State](#Props 与 State)
- 事件处理
- 条件渲染与列表渲染
- [React Hooks 全解](#React Hooks 全解)
- [React 19 新特性](#React 19 新特性)
- 组件通信与状态管理
- 路由管理
- 表单处理
- 样式方案
- 性能优化
- [React 设计模式](#React 设计模式)
- [TypeScript 与 React](#TypeScript 与 React)
- 测试
- [服务端渲染(SSR)与 Next.js](#服务端渲染(SSR)与 Next.js)
- [React 生态工具链](#React 生态工具链)
- 项目最佳实践
1. React 概述与发展历程
1.1 什么是 React
React 是由 Meta(原 Facebook)维护的开源 JavaScript UI 库,用于构建用户界面。其核心理念是组件化------将 UI 拆分为独立、可复用的组件,每个组件管理自身的状态和渲染逻辑。
React 的核心特点:
- 声明式编程:描述 UI 应该是什么样子,而非如何操作 DOM
- 组件化架构:UI 由可复用的组件树构成
- 单向数据流:数据从父组件流向子组件,使数据追踪更可预测
- 虚拟 DOM:通过内存中的虚拟 DOM 树进行高效的差异比较和最小化 DOM 更新
- 跨平台:通过 React Native 可以构建移动端应用
1.2 发展里程碑
| 版本 | 时间 | 重要特性 |
|---|---|---|
| React 0.3 | 2013.05 | 首次开源 |
| React 15 | 2016.04 | 虚拟 DOM 重写 |
| React 16 | 2017.09 | Fiber 架构、Error Boundary、Portal |
| React 16.8 | 2019.02 | Hooks 登场 |
| React 17 | 2020.10 | 无新特性,渐进式升级基础 |
| React 18 | 2022.03 | 并发渲染、Suspense、自动批处理 |
| React 19 | 2024.12 | Actions API、React Compiler、Server Components 稳定版、use() API |
| React 19.1 | 2025.06 | 迭代优化 |
| React 19.2 | 2025 下半年 | Activity、useEffectEvent 等实验性特性 |
1.3 核心架构:Fiber
React 16 引入了 Fiber 架构,将渲染工作拆分为可中断的小任务单元(Fiber 节点)。每个 Fiber 节点代表一个组件实例或 DOM 元素,包含该节点的类型、状态、子节点引用等信息。
Fiber 架构的核心优势:
- 可中断渲染:长时间渲染任务可以被暂停,让出主线程给更高优先级的任务(如用户输入)
- 优先级调度:不同类型的更新有不同的优先级
- 并发模式基础:React 18/19 的并发特性都建立在 Fiber 之上
2. 核心概念
2.1 虚拟 DOM(Virtual DOM)
虚拟 DOM 是 React 性能模型的核心。它是真实 DOM 的 JavaScript 对象表示。
工作流程:
- 状态变化 → React 创建新的虚拟 DOM 树
- Diffing:新旧虚拟 DOM 树进行比较(Reconciliation)
- Patch:计算出最小的 DOM 操作集合
- Commit:将变更批量应用到真实 DOM
Diff 算法策略:
- 不同类型的元素产生不同的树(直接替换)
- 同一层级的子元素通过
key来标识 - 开发者可以通过
key提示哪些子元素在不同渲染中保持稳定
jsx
// key 的正确使用
// ✅ 使用稳定的唯一 ID
{items.map(item => <ListItem key={item.id} data={item} />)}
// ❌ 避免使用数组索引作为 key(除非列表不会重新排序)
{items.map((item, index) => <ListItem key={index} data={item} />)}
2.2 单向数据流
React 采用单向数据流(Unidirectional Data Flow):
父组件 State
↓ (props)
子组件 → 渲染 UI
↓ (事件回调)
触发父组件 State 更新 → 重新渲染
好处:数据流向可预测,便于调试和追踪问题。
2.3 渲染流程
React 的渲染分为三个阶段:
- 触发(Trigger):状态变更、props 变更、父组件重渲染、Context 变更
- 渲染(Render):调用组件函数,生成虚拟 DOM(纯计算阶段,无副作用)
- 提交(Commit):将变更应用到真实 DOM,然后运行 effects
3. JSX 深入
3.1 JSX 是什么
JSX(JavaScript XML)是 JavaScript 的语法扩展,允许在 JavaScript 中编写类 HTML 的结构。JSX 不是模板语言,而是语法糖------最终会被编译为 React.createElement() 调用(React 17+ 使用新的 JSX Transform)。
jsx
// JSX 写法
const element = <h1 className="title">Hello, React!</h1>;
// 编译后(旧版 transform)
const element = React.createElement('h1', { className: 'title' }, 'Hello, React!');
// 编译后(React 17+ 新 JSX Transform)
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx('h1', { className: 'title', children: 'Hello, React!' });
3.2 JSX 核心规则
jsx
function MyComponent() {
const isLoggedIn = true;
const items = ['Apple', 'Banana', 'Cherry'];
const userInput = '<script>alert("xss")</script>';
return (
// 规则1: 必须有一个根元素(或使用 Fragment)
<>
{/* 规则2: 使用 className 代替 class */}
<div className="container">
{/* 规则3: 使用 htmlFor 代替 for */}
<label htmlFor="name">Name:</label>
{/* 规则4: JSX 中的 JS 表达式用 {} 包裹 */}
<p>{1 + 1}</p>
{/* 规则5: 条件渲染 */}
{isLoggedIn && <p>Welcome back!</p>}
{isLoggedIn ? <Dashboard /> : <Login />}
{/* 规则6: 列表渲染 */}
<ul>
{items.map((item, index) => (
<li key={item}>{item}</li>
))}
</ul>
{/* 规则7: style 使用对象语法 */}
<div style={{ backgroundColor: 'blue', fontSize: '16px' }}>Styled</div>
{/* 规则8: 自动 XSS 防护 - HTML 会被转义 */}
<p>{userInput}</p> {/* 安全输出为文本 */}
{/* 规则9: 自闭合标签必须闭合 */}
<img src="photo.jpg" alt="Photo" />
<br />
<input type="text" />
</div>
</>
);
}
3.3 Fragment
Fragment 允许组件返回多个元素而不需要额外的包裹 DOM 节点:
jsx
// 完整语法(支持 key)
import { Fragment } from 'react';
function Glossary({ items }) {
return items.map(item => (
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment>
));
}
// 简写语法(不支持 key)
function App() {
return (
<>
<Header />
<Main />
<Footer />
</>
);
}
4. 组件系统
4.1 函数组件(推荐)
从 React 16.8 引入 Hooks 之后,函数组件成为 React 开发的标准方式。
jsx
// 基本函数组件
function Welcome({ name }) {
return <h1>Hello, {name}!</h1>;
}
// 箭头函数写法
const Welcome = ({ name }) => <h1>Hello, {name}!</h1>;
// 默认导出
export default function App() {
return <Welcome name="React" />;
}
4.2 类组件(了解即可)
类组件是 React 早期的写法,现仍可使用但不推荐用于新项目。
jsx
import { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
// 生命周期方法
componentDidMount() {
console.log('组件已挂载');
}
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
console.log('count 已更新');
}
}
componentWillUnmount() {
console.log('组件即将卸载');
}
handleClick = () => {
this.setState(prev => ({ count: prev.count + 1 }));
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick}>+1</button>
</div>
);
}
}
4.3 组件设计原则
- 单一职责:每个组件只做一件事
- 可复用性:通过 props 使组件通用化
- 组合优于继承 :使用
children和组合模式而非继承 - 受控与非受控:明确组件的状态由谁管理
jsx
// 组合模式示例
function Card({ children, title }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}
function App() {
return (
<Card title="User Profile">
<Avatar />
<UserInfo />
</Card>
);
}
5. Props 与 State
5.1 Props
Props(Properties)是组件之间传递数据的方式,只读,不可在子组件中修改。
jsx
// Props 传递
function UserCard({ name, age, avatar, onFollow }) {
return (
<div>
<img src={avatar} alt={name} />
<h3>{name}</h3>
<p>Age: {age}</p>
<button onClick={onFollow}>Follow</button>
</div>
);
}
// 使用组件
<UserCard
name="Alice"
age={25}
avatar="/alice.jpg"
onFollow={() => console.log('Followed!')}
/>
// 默认 Props
function Button({ variant = 'primary', size = 'md', children }) {
return <button className={`btn-${variant} btn-${size}`}>{children}</button>;
}
// 展开 Props
function Wrapper(props) {
return <InnerComponent {...props} />;
}
// children prop
function Layout({ children }) {
return (
<div className="layout">
<Sidebar />
<main>{children}</main>
</div>
);
}
5.2 State
State 是组件的内部可变数据。State 的更新是异步的,且不可直接修改。
jsx
import { useState } from 'react';
function Form() {
// 基本类型
const [name, setName] = useState('');
const [count, setCount] = useState(0);
const [isOpen, setIsOpen] = useState(false);
// 对象类型 ------ 更新时必须创建新对象
const [user, setUser] = useState({ name: '', email: '' });
const updateName = (newName) => {
setUser(prev => ({ ...prev, name: newName })); // ✅ 创建新对象
// user.name = newName; // ❌ 直接修改是错误的
};
// 数组类型
const [items, setItems] = useState([]);
const addItem = (item) => setItems(prev => [...prev, item]); // 添加
const removeItem = (id) => setItems(prev => prev.filter(i => i.id !== id)); // 删除
const updateItem = (id, data) => setItems(prev =>
prev.map(i => i.id === id ? { ...i, ...data } : i) // 更新
);
// 函数式更新 ------ 当新状态依赖旧状态时使用
const increment = () => setCount(prev => prev + 1);
// 惰性初始化 ------ 仅在首次渲染时执行
const [config] = useState(() => {
return JSON.parse(localStorage.getItem('config') || '{}');
});
}
5.3 Props vs State 对比
| 特性 | Props | State |
|---|---|---|
| 所有者 | 父组件 | 当前组件 |
| 可变性 | 只读 | 可通过 setter 更新 |
| 用途 | 组件间通信 | 组件内部状态管理 |
| 更新触发渲染 | 父组件更新时 | 调用 setter 时 |
6. 事件处理
6.1 基本事件处理
React 使用合成事件(SyntheticEvent),它是对原生浏览器事件的跨浏览器包装。
jsx
function EventExample() {
// 基本点击事件
const handleClick = (e) => {
e.preventDefault(); // 阻止默认行为
e.stopPropagation(); // 阻止冒泡
console.log('Clicked!', e.target);
};
// 传参
const handleDelete = (id) => {
console.log('Delete item:', id);
};
// 输入事件
const handleChange = (e) => {
console.log('Input value:', e.target.value);
};
// 键盘事件
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
console.log('Enter pressed');
}
};
return (
<div>
<button onClick={handleClick}>Click Me</button>
<button onClick={() => handleDelete(42)}>Delete</button>
<input onChange={handleChange} onKeyDown={handleKeyDown} />
</div>
);
}
6.2 常用事件类型
| 事件类别 | 事件名 |
|---|---|
| 鼠标 | onClick, onDoubleClick, onMouseEnter, onMouseLeave, onMouseMove |
| 键盘 | onKeyDown, onKeyUp, onKeyPress |
| 表单 | onChange, onSubmit, onFocus, onBlur, onInput |
| 触摸 | onTouchStart, onTouchMove, onTouchEnd |
| 剪贴板 | onCopy, onCut, onPaste |
| 滚动 | onScroll |
| 拖拽 | onDrag, onDragStart, onDragEnd, onDrop |
7. 条件渲染与列表渲染
7.1 条件渲染
jsx
function Dashboard({ user, notifications, role }) {
// 1. if-else(适合复杂逻辑)
if (!user) {
return <LoginPage />;
}
// 2. 三元运算符(适合二选一)
const greeting = user.isNew ? <Welcome /> : <WelcomeBack />;
// 3. && 短路运算(适合有/无)
const badge = notifications.length > 0 && <Badge count={notifications.length} />;
// 4. 多条件映射
const roleComponent = {
admin: <AdminPanel />,
editor: <EditorPanel />,
viewer: <ViewerPanel />,
}[role] || <DefaultPanel />;
return (
<div>
{greeting}
{badge}
{roleComponent}
{/* 5. IIFE 模式(复杂内联逻辑) */}
{(() => {
if (user.isPremium) return <PremiumFeatures />;
if (user.isTrial) return <TrialBanner />;
return <UpgradePrompt />;
})()}
</div>
);
}
7.2 列表渲染
jsx
function TodoList({ todos }) {
if (todos.length === 0) {
return <p>No todos yet!</p>;
}
return (
<ul>
{todos.map(todo => (
<li key={todo.id} className={todo.done ? 'completed' : ''}>
<span>{todo.text}</span>
<small>{todo.createdAt}</small>
</li>
))}
</ul>
);
}
// 嵌套列表
function CategoryList({ categories }) {
return categories.map(category => (
<section key={category.id}>
<h2>{category.name}</h2>
<ul>
{category.items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</section>
));
}
Key 的最佳实践:
- 使用数据中稳定、唯一的 ID
- 不要用
Math.random()生成 key - 列表不会重排序时可用索引,但仍建议用 ID
- key 只在兄弟元素间需要唯一,不需要全局唯一
8. React Hooks 全解
Hooks 是 React 16.8 引入的特性,允许在函数组件中使用状态和其他 React 特性。
8.1 Hooks 规则
- 只在函数组件或自定义 Hook 的顶层调用 Hooks
- 不要在循环、条件或嵌套函数中调用 Hooks
- 自定义 Hook 必须以
use开头
jsx
function MyComponent({ condition }) {
// ✅ 正确
const [value, setValue] = useState(0);
// ❌ 错误 - 不要在条件中调用
// if (condition) {
// const [other, setOther] = useState('');
// }
}
8.2 useState ------ 状态管理
jsx
import { useState } from 'react';
function CompleteStateExample() {
// 基本用法
const [count, setCount] = useState(0);
// 对象状态
const [form, setForm] = useState({
username: '',
email: '',
password: '',
});
// 数组状态
const [todos, setTodos] = useState([]);
// 函数式更新(基于前一个状态计算新状态)
const increment = () => setCount(prev => prev + 1);
// 批量更新多次调用
const incrementThree = () => {
setCount(prev => prev + 1); // 0 → 1
setCount(prev => prev + 1); // 1 → 2
setCount(prev => prev + 1); // 2 → 3
};
// 更新对象中的某个字段
const handleInputChange = (field) => (e) => {
setForm(prev => ({
...prev,
[field]: e.target.value,
}));
};
// 惰性初始化
const [data] = useState(() => expensiveComputation());
return (
<form>
<input value={form.username} onChange={handleInputChange('username')} />
<input value={form.email} onChange={handleInputChange('email')} />
<p>Count: {count}</p>
<button type="button" onClick={increment}>+1</button>
</form>
);
}
8.3 useEffect ------ 副作用管理
useEffect 用于处理副作用:数据获取、订阅、DOM 操作等。
jsx
import { useState, useEffect } from 'react';
function EffectExamples({ userId }) {
const [user, setUser] = useState(null);
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
// 1. 每次渲染后执行(无依赖数组)
useEffect(() => {
document.title = `User: ${user?.name || 'Loading'}`;
});
// 2. 仅在挂载时执行(空依赖数组)
useEffect(() => {
console.log('Component mounted');
return () => console.log('Component unmounted'); // 清理函数
}, []);
// 3. 依赖变化时执行
useEffect(() => {
let cancelled = false; // 防止竞态条件
async function fetchUser() {
const res = await fetch(`/api/users/${userId}`);
const data = await res.json();
if (!cancelled) {
setUser(data);
}
}
fetchUser();
// 清理函数:组件卸载或 userId 变化时执行
return () => {
cancelled = true;
};
}, [userId]);
// 4. 事件订阅与清理
useEffect(() => {
const handleResize = () => setWindowWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// 5. 定时器
useEffect(() => {
const timer = setInterval(() => {
console.log('tick');
}, 1000);
return () => clearInterval(timer);
}, []);
}
useEffect 依赖数组总结:
| 形式 | 执行时机 |
|---|---|
useEffect(fn) |
每次渲染后 |
useEffect(fn, []) |
仅挂载和卸载 |
useEffect(fn, [a, b]) |
挂载 + a 或 b 变化时 |
8.4 useContext ------ 上下文
跨层级共享数据,避免 props 逐层传递(prop drilling)。
jsx
import { createContext, useContext, useState } from 'react';
// 1. 创建 Context
const ThemeContext = createContext('light');
// 2. 提供 Provider
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// 3. 在任意后代组件中消费
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{
background: theme === 'dark' ? '#333' : '#fff',
color: theme === 'dark' ? '#fff' : '#333',
}}
>
Current: {theme}
</button>
);
}
// 4. 在 App 中使用
function App() {
return (
<ThemeProvider>
<Header />
<Main />
<ThemedButton />
</ThemeProvider>
);
}
8.5 useReducer ------ 复杂状态逻辑
适用于状态逻辑复杂、包含多个子值、下一个状态依赖前一个状态的场景。
jsx
import { useReducer } from 'react';
// 定义 action 类型
const ACTIONS = {
ADD_TODO: 'ADD_TODO',
TOGGLE_TODO: 'TOGGLE_TODO',
DELETE_TODO: 'DELETE_TODO',
EDIT_TODO: 'EDIT_TODO',
};
// Reducer 函数(纯函数)
function todoReducer(state, action) {
switch (action.type) {
case ACTIONS.ADD_TODO:
return [
...state,
{ id: Date.now(), text: action.payload, completed: false },
];
case ACTIONS.TOGGLE_TODO:
return state.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
);
case ACTIONS.DELETE_TODO:
return state.filter(todo => todo.id !== action.payload);
case ACTIONS.EDIT_TODO:
return state.map(todo =>
todo.id === action.payload.id
? { ...todo, text: action.payload.text }
: todo
);
default:
return state;
}
}
function TodoApp() {
const [todos, dispatch] = useReducer(todoReducer, []);
const addTodo = (text) => {
dispatch({ type: ACTIONS.ADD_TODO, payload: text });
};
const toggleTodo = (id) => {
dispatch({ type: ACTIONS.TOGGLE_TODO, payload: id });
};
return (
<div>
<button onClick={() => addTodo('New Task')}>Add</button>
{todos.map(todo => (
<div key={todo.id} onClick={() => toggleTodo(todo.id)}>
{todo.completed ? '✅' : '⬜'} {todo.text}
</div>
))}
</div>
);
}
8.6 useRef ------ 引用
useRef 返回一个可变的 ref 对象,其 .current 属性在组件的整个生命周期内持久存在,修改它不会触发重新渲染。
jsx
import { useRef, useEffect, useState } from 'react';
function RefExamples() {
// 1. 访问 DOM 元素
const inputRef = useRef(null);
const focusInput = () => inputRef.current.focus();
// 2. 存储可变值(不触发重渲染)
const renderCount = useRef(0);
useEffect(() => {
renderCount.current += 1;
});
// 3. 保存前一个值
const [count, setCount] = useState(0);
const prevCount = useRef(count);
useEffect(() => {
prevCount.current = count;
}, [count]);
// 4. 存储定时器 ID
const timerRef = useRef(null);
const startTimer = () => {
timerRef.current = setInterval(() => console.log('tick'), 1000);
};
const stopTimer = () => clearInterval(timerRef.current);
return (
<div>
<input ref={inputRef} />
<button onClick={focusInput}>Focus</button>
<p>Render count: {renderCount.current}</p>
<p>Current: {count}, Previous: {prevCount.current}</p>
</div>
);
}
8.7 useMemo 与 useCallback ------ 性能优化
jsx
import { useMemo, useCallback, useState } from 'react';
function OptimizedComponent({ items, onItemClick }) {
const [filter, setFilter] = useState('');
// useMemo: 缓存计算结果
const filteredItems = useMemo(() => {
console.log('Filtering items...'); // 仅在 items 或 filter 变化时执行
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [items, filter]);
// useMemo: 缓存复杂对象
const chartData = useMemo(() => {
return processDataForChart(items);
}, [items]);
// useCallback: 缓存函数引用
const handleClick = useCallback((id) => {
onItemClick(id);
}, [onItemClick]);
// useCallback: 配合 React.memo 的子组件使用
const handleDelete = useCallback((id) => {
setItems(prev => prev.filter(item => item.id !== id));
}, []);
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
{filteredItems.map(item => (
<MemoizedItem
key={item.id}
item={item}
onClick={handleClick}
onDelete={handleDelete}
/>
))}
</div>
);
}
// React.memo 包裹的子组件
const MemoizedItem = React.memo(function Item({ item, onClick, onDelete }) {
return (
<div>
<span onClick={() => onClick(item.id)}>{item.name}</span>
<button onClick={() => onDelete(item.id)}>Delete</button>
</div>
);
});
何时使用 useMemo / useCallback:
- 计算开销大的操作(大列表过滤、排序、复杂数学运算)
- 传递给被
React.memo包裹的子组件的 props - 作为其他 Hooks 的依赖项
- 不要过度使用------简单计算的 memo 开销可能大于重复计算
8.8 useImperativeHandle
自定义通过 ref 暴露给父组件的实例值。
jsx
import { forwardRef, useImperativeHandle, useRef } from 'react';
const FancyInput = forwardRef(function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView({ behavior: 'smooth' });
},
getValue() {
return inputRef.current.value;
},
}), []);
return <input ref={inputRef} {...props} />;
});
// 父组件使用
function Parent() {
const fancyRef = useRef();
return (
<div>
<FancyInput ref={fancyRef} />
<button onClick={() => fancyRef.current.focus()}>Focus Input</button>
</div>
);
}
注意:React 19 中,ref 可以直接作为 props 传递给函数组件,不再必须使用 forwardRef。
8.9 useLayoutEffect
与 useEffect 类似,但在 DOM 更新后、浏览器绘制前同步执行。适用于需要读取 DOM 布局并同步触发重渲染的场景。
jsx
import { useLayoutEffect, useRef, useState } from 'react';
function Tooltip({ children, text }) {
const [tooltipHeight, setTooltipHeight] = useState(0);
const tooltipRef = useRef();
// 在浏览器绘制前测量 DOM
useLayoutEffect(() => {
const { height } = tooltipRef.current.getBoundingClientRect();
setTooltipHeight(height);
}, []);
return (
<div>
<div ref={tooltipRef} style={{ position: 'absolute', top: -tooltipHeight }}>
{text}
</div>
{children}
</div>
);
}
8.10 其他内置 Hooks
jsx
// useId - 生成稳定的唯一 ID(SSR 安全)
import { useId } from 'react';
function FormField({ label }) {
const id = useId();
return (
<>
<label htmlFor={id}>{label}</label>
<input id={id} />
</>
);
}
// useTransition - 标记非紧急更新
import { useTransition, useState } from 'react';
function Search() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
setQuery(e.target.value); // 紧急更新
startTransition(() => {
setResults(filterResults(e.target.value)); // 非紧急更新
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<ResultList results={results} />
</div>
);
}
// useDeferredValue - 延迟更新值
import { useDeferredValue } from 'react';
function FilteredList({ filter }) {
const deferredFilter = useDeferredValue(filter);
// deferredFilter 在高优先级更新完成后才更新
const items = useMemo(() => filterItems(deferredFilter), [deferredFilter]);
return <ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
}
// useSyncExternalStore - 订阅外部 store
import { useSyncExternalStore } from 'react';
function useOnlineStatus() {
return useSyncExternalStore(
(callback) => {
window.addEventListener('online', callback);
window.addEventListener('offline', callback);
return () => {
window.removeEventListener('online', callback);
window.removeEventListener('offline', callback);
};
},
() => navigator.onLine, // 客户端快照
() => true // 服务端快照
);
}
8.11 自定义 Hook
自定义 Hook 是以 use 开头的函数,可以内部调用其他 Hook,用于逻辑复用。
jsx
// useFetch - 通用数据获取 Hook
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetch(url)
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(json => {
if (!cancelled) {
setData(json);
setError(null);
}
})
.catch(err => {
if (!cancelled) setError(err.message);
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => { cancelled = true; };
}, [url]);
return { data, loading, error };
}
// useLocalStorage - 持久化状态
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
const setValue = (value) => {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
localStorage.setItem(key, JSON.stringify(valueToStore));
};
return [storedValue, setValue];
}
// useDebounce - 防抖
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// useMediaQuery - 响应式
function useMediaQuery(query) {
const [matches, setMatches] = useState(
() => window.matchMedia(query).matches
);
useEffect(() => {
const mql = window.matchMedia(query);
const handler = (e) => setMatches(e.matches);
mql.addEventListener('change', handler);
return () => mql.removeEventListener('change', handler);
}, [query]);
return matches;
}
// 使用示例
function App() {
const { data, loading, error } = useFetch('/api/posts');
const [theme, setTheme] = useLocalStorage('theme', 'light');
const isMobile = useMediaQuery('(max-width: 768px)');
if (loading) return <Spinner />;
if (error) return <ErrorMessage message={error} />;
return <PostList posts={data} layout={isMobile ? 'stack' : 'grid'} />;
}
9. React 19 新特性
React 19 于 2024 年 12 月正式发布,是一次重要的大版本更新。
9.1 Actions API
Actions 简化了异步数据变更的处理,自动管理 pending 状态、错误处理和乐观更新。
jsx
import { useTransition, useState } from 'react';
function UpdateProfile() {
const [name, setName] = useState('');
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();
const handleSubmit = () => {
startTransition(async () => {
const result = await updateProfileAPI(name);
if (result.error) {
setError(result.error);
}
});
};
return (
<div>
<input value={name} onChange={e => setName(e.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
{isPending ? 'Saving...' : 'Save'}
</button>
{error && <p className="error">{error}</p>}
</div>
);
}
9.2 useActionState
用于管理表单 action 的状态:
jsx
import { useActionState } from 'react';
function LoginForm() {
const [state, formAction, isPending] = useActionState(
async (prevState, formData) => {
const email = formData.get('email');
const password = formData.get('password');
try {
await loginAPI(email, password);
return { success: true, error: null };
} catch (err) {
return { success: false, error: err.message };
}
},
{ success: false, error: null } // 初始状态
);
return (
<form action={formAction}>
<input name="email" type="email" required />
<input name="password" type="password" required />
<button disabled={isPending}>
{isPending ? 'Logging in...' : 'Log In'}
</button>
{state.error && <p className="error">{state.error}</p>}
{state.success && <p className="success">Welcome!</p>}
</form>
);
}
9.3 useOptimistic
实现乐观 UI 更新------在服务端确认前先更新 UI:
jsx
import { useOptimistic, useState } from 'react';
function MessageList({ messages, sendMessage }) {
const [optimisticMessages, addOptimistic] = useOptimistic(
messages,
(currentMessages, newMessage) => [
...currentMessages,
{ ...newMessage, sending: true },
]
);
const handleSend = async (text) => {
const newMsg = { id: Date.now(), text };
addOptimistic(newMsg); // 立即显示
await sendMessage(newMsg); // 发送到服务端
};
return (
<ul>
{optimisticMessages.map(msg => (
<li key={msg.id} style={{ opacity: msg.sending ? 0.6 : 1 }}>
{msg.text}
{msg.sending && ' (sending...)'}
</li>
))}
</ul>
);
}
9.4 use() API
新的 use API 可以在渲染期间读取 Promise 和 Context:
jsx
import { use, Suspense } from 'react';
// 读取 Promise
function UserProfile({ userPromise }) {
const user = use(userPromise); // React 会 Suspend 直到 Promise resolve
return <h1>{user.name}</h1>;
}
function App() {
const userPromise = fetchUser(1); // 在外部发起请求
return (
<Suspense fallback={<Loading />}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}
// 条件性读取 Context(这是 use 相比 useContext 的优势)
function StatusMessage({ isEnabled }) {
if (isEnabled) {
const theme = use(ThemeContext); // 可以在条件内调用
return <p style={{ color: theme.primary }}>Enabled</p>;
}
return <p>Disabled</p>;
}
9.5 React Compiler(实验性)
React Compiler 在构建时自动优化组件,减少不必要的重渲染,开发者无需手动编写 useMemo、useCallback 等。
jsx
// 之前需要手动优化
const MemoizedComponent = React.memo(function Component({ data }) {
const processed = useMemo(() => expensiveCalc(data), [data]);
const handler = useCallback(() => doSomething(data), [data]);
return <Child data={processed} onClick={handler} />;
});
// React Compiler 之后,直接写即可
function Component({ data }) {
const processed = expensiveCalc(data);
const handler = () => doSomething(data);
return <Child data={processed} onClick={handler} />;
}
// 编译器自动决定何处需要 memoization
9.6 Server Components
Server Components 在服务端运行,不向客户端发送 JavaScript,用于数据获取和静态内容渲染。
jsx
// ServerComponent.jsx (默认是 Server Component)
// 可以直接使用 async/await
async function BlogPost({ id }) {
const post = await db.posts.findOne({ id }); // 直接访问数据库
const comments = await db.comments.find({ postId: id });
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
<CommentList comments={comments} />
<AddComment postId={id} /> {/* 客户端组件 */}
</article>
);
}
// AddComment.jsx
'use client' // 标记为客户端组件
import { useState } from 'react';
function AddComment({ postId }) {
const [text, setText] = useState('');
// 可以使用 Hooks、事件处理、浏览器 API
return (
<form onSubmit={handleSubmit}>
<textarea value={text} onChange={e => setText(e.target.value)} />
<button>Submit</button>
</form>
);
}
Server Components vs Client Components:
| 特性 | Server Components | Client Components |
|---|---|---|
| 运行环境 | 服务端 | 客户端 |
| 指令 | 默认 | 'use client' |
| 可用 Hooks | 否 | 是 |
| 可用浏览器 API | 否 | 是 |
| 直接访问数据库 | 是 | 否 |
| 打包到客户端 JS | 否 | 是 |
| 可使用 async/await | 是 | 否(需在 useEffect 中) |
9.7 其他 React 19 改进
- ref 作为 prop:函数组件可直接接收 ref,无需 forwardRef
- ref cleanup 函数:ref 回调可返回清理函数
- 文档元数据支持 :直接在组件中渲染
<title>,<meta>,<link>,React 自动提升到<head> - 资源预加载 API :
prefetchDNS,preconnect,preload,preinit等 - 样式表优先级 :
<link>的precedence属性控制样式表加载顺序 - 增强的错误报告:更好的 hydration 错误信息
jsx
// ref 作为 prop(React 19)
function MyInput({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
// ref cleanup
<div ref={(node) => {
// 设置 ref
node.addEventListener('scroll', handleScroll);
// 返回清理函数
return () => node.removeEventListener('scroll', handleScroll);
}} />
// 文档元数据
function BlogPost({ post }) {
return (
<article>
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
<link rel="canonical" href={post.url} />
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
10. 组件通信与状态管理
10.1 组件通信方式总结
| 方式 | 适用场景 |
|---|---|
| Props | 父 → 子 |
| 回调函数 Props | 子 → 父 |
| Context | 跨层级共享 |
| Ref | 父访问子组件实例/DOM |
| 状态提升 | 兄弟组件共享数据 |
| 状态管理库 | 复杂全局状态 |
10.2 状态提升
当多个组件需要共享状态时,将状态提升到最近的共同祖先:
jsx
function TemperatureConverter() {
const [celsius, setCelsius] = useState('');
const fahrenheit = celsius ? (parseFloat(celsius) * 9/5 + 32).toFixed(1) : '';
return (
<div>
<TemperatureInput
scale="Celsius"
value={celsius}
onChange={setCelsius}
/>
<TemperatureInput
scale="Fahrenheit"
value={fahrenheit}
onChange={(f) => setCelsius(((parseFloat(f) - 32) * 5/9).toFixed(1))}
/>
</div>
);
}
10.3 useReducer + Context 全局状态
适合中等复杂度的应用,无需引入外部库:
jsx
import { createContext, useContext, useReducer } from 'react';
// 定义状态和 actions
const initialState = {
user: null,
theme: 'light',
notifications: [],
};
function appReducer(state, action) {
switch (action.type) {
case 'LOGIN':
return { ...state, user: action.payload };
case 'LOGOUT':
return { ...state, user: null };
case 'TOGGLE_THEME':
return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
case 'ADD_NOTIFICATION':
return { ...state, notifications: [...state.notifications, action.payload] };
default:
return state;
}
}
// 创建 Context
const AppContext = createContext();
const AppDispatchContext = createContext();
// Provider
function AppProvider({ children }) {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<AppContext.Provider value={state}>
<AppDispatchContext.Provider value={dispatch}>
{children}
</AppDispatchContext.Provider>
</AppContext.Provider>
);
}
// 自定义 Hooks 简化使用
function useAppState() {
return useContext(AppContext);
}
function useAppDispatch() {
return useContext(AppDispatchContext);
}
// 在组件中使用
function UserMenu() {
const { user } = useAppState();
const dispatch = useAppDispatch();
if (!user) return <button onClick={() => dispatch({ type: 'LOGIN', payload: { name: 'Alice' } })}>Login</button>;
return <span>{user.name} <button onClick={() => dispatch({ type: 'LOGOUT' })}>Logout</button></span>;
}
10.4 主流状态管理库对比
| 库 | 特点 | 适用场景 |
|---|---|---|
| Zustand | 极简 API,不需要 Provider | 中小型项目,替代 Redux |
| Redux Toolkit | 成熟生态,DevTools 强大 | 大型项目,需要严格状态管理 |
| Jotai | 原子化状态,自下而上 | 细粒度状态,避免不必要重渲染 |
| Recoil | Facebook 出品,原子 + 选择器 | 复杂派生状态 |
| TanStack Query | 专注服务端状态(缓存、同步) | 数据获取密集型应用 |
| Valtio | 基于 Proxy 的可变风格 | 偏好可变编程风格 |
jsx
// Zustand 示例
import { create } from 'zustand';
const useStore = create((set, get) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
// 异步 action
fetchCount: async () => {
const res = await fetch('/api/count');
const data = await res.json();
set({ count: data.count });
},
}));
function Counter() {
const { count, increment, decrement } = useStore();
return (
<div>
<span>{count}</span>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
}
jsx
// TanStack Query 示例
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function Posts() {
const queryClient = useQueryClient();
// 查询
const { data: posts, isLoading, error } = useQuery({
queryKey: ['posts'],
queryFn: () => fetch('/api/posts').then(r => r.json()),
staleTime: 5 * 60 * 1000, // 5 分钟内不重新请求
gcTime: 30 * 60 * 1000, // 缓存保留 30 分钟
});
// 变更
const mutation = useMutation({
mutationFn: (newPost) => fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(newPost),
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] }); // 自动刷新
},
});
if (isLoading) return <Spinner />;
if (error) return <Error message={error.message} />;
return (
<div>
{posts.map(post => <PostCard key={post.id} post={post} />)}
<button onClick={() => mutation.mutate({ title: 'New Post' })}>
Add Post
</button>
</div>
);
}
11. 路由管理
11.1 React Router v6+
jsx
import { BrowserRouter, Routes, Route, Link, NavLink, Outlet, useParams, useNavigate, useSearchParams } from 'react-router-dom';
// 基本路由配置
function App() {
return (
<BrowserRouter>
<nav>
<NavLink to="/" className={({ isActive }) => isActive ? 'active' : ''}>
Home
</NavLink>
<NavLink to="/about">About</NavLink>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* 嵌套路由 */}
<Route path="/users" element={<UsersLayout />}>
<Route index element={<UserList />} />
<Route path=":userId" element={<UserDetail />} />
<Route path=":userId/edit" element={<UserEdit />} />
</Route>
{/* 受保护路由 */}
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
{/* 404 */}
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
// 嵌套路由布局
function UsersLayout() {
return (
<div className="users-layout">
<h1>Users</h1>
<Outlet /> {/* 子路由在此渲染 */}
</div>
);
}
// 路由参数
function UserDetail() {
const { userId } = useParams();
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
const tab = searchParams.get('tab') || 'profile';
return (
<div>
<h2>User {userId}</h2>
<button onClick={() => navigate(-1)}>Back</button>
<button onClick={() => navigate('/users')}>User List</button>
<button onClick={() => setSearchParams({ tab: 'settings' })}>
Settings
</button>
</div>
);
}
// 路由守卫
function ProtectedRoute({ children }) {
const { user } = useAuth();
const location = useLocation();
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// 代码分割 + 路由懒加载
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
12. 表单处理
12.1 受控组件
jsx
function RegistrationForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
role: 'user',
agree: false,
});
const [errors, setErrors] = useState({});
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value,
}));
// 清除错误
if (errors[name]) {
setErrors(prev => ({ ...prev, [name]: '' }));
}
};
const validate = () => {
const newErrors = {};
if (!formData.name.trim()) newErrors.name = 'Name is required';
if (!/^\S+@\S+\.\S+$/.test(formData.email)) newErrors.email = 'Invalid email';
if (formData.password.length < 8) newErrors.password = 'Min 8 characters';
if (!formData.agree) newErrors.agree = 'Must agree to terms';
return newErrors;
};
const handleSubmit = (e) => {
e.preventDefault();
const validationErrors = validate();
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
return;
}
console.log('Submit:', formData);
};
return (
<form onSubmit={handleSubmit}>
<div>
<input name="name" value={formData.name} onChange={handleChange} placeholder="Name" />
{errors.name && <span className="error">{errors.name}</span>}
</div>
<div>
<input name="email" type="email" value={formData.email} onChange={handleChange} />
{errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<input name="password" type="password" value={formData.password} onChange={handleChange} />
{errors.password && <span className="error">{errors.password}</span>}
</div>
<select name="role" value={formData.role} onChange={handleChange}>
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
<label>
<input name="agree" type="checkbox" checked={formData.agree} onChange={handleChange} />
I agree to the terms
{errors.agree && <span className="error">{errors.agree}</span>}
</label>
<button type="submit">Register</button>
</form>
);
}
12.2 非受控组件
jsx
function UncontrolledForm() {
const nameRef = useRef();
const fileRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
console.log('Name:', nameRef.current.value);
console.log('File:', fileRef.current.files[0]);
};
return (
<form onSubmit={handleSubmit}>
<input ref={nameRef} defaultValue="default" />
<input ref={fileRef} type="file" />
<button>Submit</button>
</form>
);
}
12.3 使用 React Hook Form
对于复杂表单,推荐使用 React Hook Form 等库减少样板代码:
jsx
import { useForm } from 'react-hook-form';
function FormWithLibrary() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
watch,
reset,
} = useForm({
defaultValues: { name: '', email: '' },
});
const onSubmit = async (data) => {
await submitToAPI(data);
reset();
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register('name', {
required: 'Name is required',
minLength: { value: 2, message: 'Min 2 characters' },
})}
/>
{errors.name && <span>{errors.name.message}</span>}
<input
{...register('email', {
required: 'Email is required',
pattern: { value: /^\S+@\S+$/i, message: 'Invalid email' },
})}
/>
{errors.email && <span>{errors.email.message}</span>}
<button disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
}
13. 样式方案
13.1 方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| CSS Modules | 局部作用域、零运行时 | 需要构建工具支持 |
| Tailwind CSS | 快速开发、一致性 | 类名较长 |
| CSS-in-JS (styled-components) | 动态样式、组件封装 | 运行时开销 |
| Vanilla CSS / Sass | 熟悉、简单 | 全局命名冲突 |
13.2 CSS Modules
jsx
// Button.module.css
// .button { padding: 8px 16px; border-radius: 4px; }
// .primary { background: #007bff; color: white; }
// .secondary { background: #6c757d; color: white; }
import styles from './Button.module.css';
function Button({ variant = 'primary', children }) {
return (
<button className={`${styles.button} ${styles[variant]}`}>
{children}
</button>
);
}
13.3 Tailwind CSS
jsx
function Card({ title, children }) {
return (
<div className="rounded-lg shadow-md p-6 bg-white dark:bg-gray-800
hover:shadow-lg transition-shadow duration-300">
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">
{title}
</h2>
<div className="text-gray-600 dark:text-gray-300">
{children}
</div>
</div>
);
}
13.4 styled-components
jsx
import styled from 'styled-components';
const StyledButton = styled.button`
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
font-size: ${props => props.size === 'lg' ? '18px' : '14px'};
background: ${props => props.variant === 'primary' ? '#007bff' : '#6c757d'};
color: white;
&:hover {
opacity: 0.9;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
`;
function App() {
return <StyledButton variant="primary" size="lg">Click Me</StyledButton>;
}
14. 性能优化
14.1 理解重渲染
组件重渲染的触发条件:
- 自身状态变化
- 父组件重渲染(即使子组件 props 未变)
- 所消费的 Context 值变化
- 自定义 Hook 中的状态变化
14.2 避免不必要的重渲染
jsx
import { memo, useMemo, useCallback } from 'react';
// 1. React.memo ------ 浅比较 props
const ExpensiveList = memo(function ExpensiveList({ items, onSelect }) {
return items.map(item => (
<div key={item.id} onClick={() => onSelect(item.id)}>
{item.name}
</div>
));
});
// 2. 自定义比较函数
const DeepMemoComponent = memo(
function MyComponent({ config }) {
return <div>{config.title}</div>;
},
(prevProps, nextProps) => {
return prevProps.config.id === nextProps.config.id;
}
);
// 3. 状态下沉 ------ 将频繁变化的状态放在更低层级
// ❌ 不好:整个页面因为鼠标位置更新
function Page() {
const [position, setPosition] = useState({ x: 0, y: 0 });
return (
<div onMouseMove={e => setPosition({ x: e.clientX, y: e.clientY })}>
<Cursor position={position} />
<ExpensiveComponent /> {/* 不必要地重渲染 */}
</div>
);
}
// ✅ 好:将状态隔离到子组件
function Page() {
return (
<div>
<MouseTracker /> {/* 状态在这里面 */}
<ExpensiveComponent /> {/* 不受影响 */}
</div>
);
}
14.3 代码分割与懒加载
jsx
import { lazy, Suspense, startTransition } from 'react';
// 路由级代码分割
const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
// 带 prefetch 的懒加载
const AdminPanel = lazy(() => import(/* webpackPrefetch: true */ './pages/Admin'));
// Suspense 边界
function App() {
return (
<Suspense fallback={<FullPageLoader />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={
<Suspense fallback={<DashboardSkeleton />}>
<Dashboard />
</Suspense>
} />
</Routes>
</Suspense>
);
}
14.4 列表虚拟化
渲染大量列表时,只渲染可视区域内的元素:
jsx
import { FixedSizeList as List } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style} className="list-row">
{items[index].name}
</div>
);
return (
<List
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</List>
);
}
14.5 并发特性优化
jsx
import { useTransition, useDeferredValue } from 'react';
function SearchWithTransition() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleSearch = (e) => {
const value = e.target.value;
setQuery(value); // 高优先级:立即更新输入框
startTransition(() => {
// 低优先级:可以被中断
const filtered = hugeDataset.filter(item =>
item.name.includes(value)
);
setResults(filtered);
});
};
return (
<div>
<input value={query} onChange={handleSearch} />
{isPending && <Spinner />}
<ul style={{ opacity: isPending ? 0.7 : 1 }}>
{results.map(r => <li key={r.id}>{r.name}</li>)}
</ul>
</div>
);
}
14.6 性能优化清单
| 类别 | 优化手段 |
|---|---|
| 渲染 | React.memo, useMemo, useCallback, 状态下沉 |
| 加载 | 代码分割, lazy/Suspense, 预加载关键资源 |
| 列表 | 虚拟化(react-window/react-virtuoso), 稳定 key |
| 网络 | 数据缓存(TanStack Query), 防抖/节流 |
| 打包 | Tree shaking, 按需导入, 分析 bundle 大小 |
| 架构 | Server Components, SSR/SSG, 流式渲染 |
| 工具 | React DevTools Profiler, Lighthouse, Web Vitals |
15. React 设计模式
15.1 容器/展示组件模式
jsx
// 展示组件(纯 UI)
function UserCard({ user, onFollow }) {
return (
<div className="user-card">
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<button onClick={() => onFollow(user.id)}>Follow</button>
</div>
);
}
// 容器组件(逻辑)
function UserCardContainer({ userId }) {
const { data: user, isLoading } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
const followMutation = useMutation({ mutationFn: followUser });
if (isLoading) return <UserCardSkeleton />;
return <UserCard user={user} onFollow={followMutation.mutate} />;
}
15.2 Compound Components(复合组件)
jsx
import { createContext, useContext, useState } from 'react';
const TabsContext = createContext();
function Tabs({ children, defaultTab }) {
const [activeTab, setActiveTab] = useState(defaultTab);
return (
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
function TabList({ children }) {
return <div className="tab-list">{children}</div>;
}
function Tab({ value, children }) {
const { activeTab, setActiveTab } = useContext(TabsContext);
return (
<button
className={activeTab === value ? 'active' : ''}
onClick={() => setActiveTab(value)}
>
{children}
</button>
);
}
function TabPanel({ value, children }) {
const { activeTab } = useContext(TabsContext);
if (activeTab !== value) return null;
return <div className="tab-panel">{children}</div>;
}
// 使用
function App() {
return (
<Tabs defaultTab="profile">
<TabList>
<Tab value="profile">Profile</Tab>
<Tab value="settings">Settings</Tab>
<Tab value="billing">Billing</Tab>
</TabList>
<TabPanel value="profile"><ProfilePage /></TabPanel>
<TabPanel value="settings"><SettingsPage /></TabPanel>
<TabPanel value="billing"><BillingPage /></TabPanel>
</Tabs>
);
}
15.3 Render Props
jsx
function MouseTracker({ render }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
return (
<div onMouseMove={handleMouseMove} style={{ height: '100vh' }}>
{render(position)}
</div>
);
}
// 使用
<MouseTracker
render={({ x, y }) => (
<p>Mouse: ({x}, {y})</p>
)}
/>
注意:大多数 Render Props 场景现在可以用自定义 Hook 替代,更简洁。
15.4 高阶组件(HOC)
jsx
function withAuth(WrappedComponent) {
return function AuthenticatedComponent(props) {
const { user, loading } = useAuth();
if (loading) return <Spinner />;
if (!user) return <Navigate to="/login" />;
return <WrappedComponent {...props} user={user} />;
};
}
const ProtectedDashboard = withAuth(Dashboard);
注意:HOC 模式在 Hooks 时代使用减少,大多可用自定义 Hook 替代。
15.5 Error Boundary
jsx
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error caught:', error, errorInfo);
// 上报错误到监控平台
reportError(error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="error-fallback">
<h2>Something went wrong</h2>
<button onClick={() => this.setState({ hasError: false })}>
Try again
</button>
</div>
);
}
return this.props.children;
}
}
// 使用
function App() {
return (
<ErrorBoundary fallback={<AppErrorPage />}>
<Header />
<ErrorBoundary fallback={<ContentError />}>
<MainContent />
</ErrorBoundary>
<Footer />
</ErrorBoundary>
);
}
16. TypeScript 与 React
16.1 基本类型定义
tsx
// Props 类型
interface UserCardProps {
name: string;
age: number;
avatar?: string; // 可选
role: 'admin' | 'user'; // 联合类型
tags: string[]; // 数组
onFollow: (id: string) => void; // 函数
children: React.ReactNode; // React 子元素
}
function UserCard({ name, age, avatar, role, tags, onFollow, children }: UserCardProps) {
return <div>{children}</div>;
}
// 带泛型的组件
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
// 使用
<List
items={users}
renderItem={(user) => <span>{user.name}</span>}
keyExtractor={(user) => user.id}
/>
16.2 Hooks 类型
tsx
// useState
const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null);
// useRef
const inputRef = useRef<HTMLInputElement>(null);
const timerRef = useRef<number | null>(null);
// useReducer
type Action =
| { type: 'INCREMENT' }
| { type: 'DECREMENT' }
| { type: 'SET'; payload: number };
function reducer(state: number, action: Action): number {
switch (action.type) {
case 'INCREMENT': return state + 1;
case 'DECREMENT': return state - 1;
case 'SET': return action.payload;
}
}
const [count, dispatch] = useReducer(reducer, 0);
// useContext
interface AuthContextType {
user: User | null;
login: (credentials: Credentials) => Promise<void>;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | null>(null);
function useAuth(): AuthContextType {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
}
// 事件类型
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {};
17. 测试
17.1 测试工具
- Vitest / Jest:测试运行器和断言库
- React Testing Library:以用户视角测试组件
- Playwright / Cypress:端到端测试
- MSW (Mock Service Worker):API mock
17.2 React Testing Library 示例
jsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Counter } from './Counter';
import { LoginForm } from './LoginForm';
// 基本渲染测试
test('renders counter with initial value', () => {
render(<Counter initialCount={5} />);
expect(screen.getByText('Count: 5')).toBeInTheDocument();
});
// 用户交互测试
test('increments counter on button click', async () => {
const user = userEvent.setup();
render(<Counter initialCount={0} />);
await user.click(screen.getByRole('button', { name: /increment/i }));
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
// 异步测试
test('submits login form', async () => {
const mockLogin = jest.fn().mockResolvedValue({ success: true });
const user = userEvent.setup();
render(<LoginForm onSubmit={mockLogin} />);
await user.type(screen.getByLabelText(/email/i), 'test@example.com');
await user.type(screen.getByLabelText(/password/i), 'password123');
await user.click(screen.getByRole('button', { name: /log in/i }));
await waitFor(() => {
expect(mockLogin).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123',
});
});
});
// 自定义 Hook 测试
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
test('useCounter hook', () => {
const { result } = renderHook(() => useCounter(0));
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
17.3 测试最佳实践
- 测试用户行为而非实现细节
- 使用
getByRole,getByLabelText等语义查询 - 避免测试内部状态,测试渲染输出
- 对关键路径编写端到端测试
- 测试覆盖率目标 80% 以上
18. 服务端渲染(SSR)与 Next.js
18.1 渲染策略对比
| 策略 | 简称 | 说明 | 适用场景 |
|---|---|---|---|
| 客户端渲染 | CSR | 浏览器执行 JS 渲染 | SPA、后台系统 |
| 服务端渲染 | SSR | 每次请求在服务端渲染 | SEO 重要的动态页面 |
| 静态生成 | SSG | 构建时生成 HTML | 博客、文档、营销页 |
| 增量静态再生成 | ISR | 定期在后台重新生成 | 电商、内容平台 |
| 流式 SSR | Streaming | 分块发送 HTML | 首屏优化 |
18.2 Next.js App Router 基础
app/
├── layout.tsx # 根布局
├── page.tsx # 首页 /
├── loading.tsx # 加载状态
├── error.tsx # 错误处理
├── not-found.tsx # 404 页面
├── blog/
│ ├── page.tsx # /blog
│ └── [slug]/
│ └── page.tsx # /blog/:slug
└── api/
└── users/
└── route.ts # API 路由 /api/users
tsx
// app/layout.tsx - 根布局(Server Component)
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Header />
<main>{children}</main>
<Footer />
</body>
</html>
);
}
// app/blog/[slug]/page.tsx - 动态路由(Server Component)
interface PageProps {
params: Promise<{ slug: string }>;
}
export default async function BlogPost({ params }: PageProps) {
const { slug } = await params;
const post = await fetchPost(slug);
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
// 生成静态路径(SSG)
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map(post => ({ slug: post.slug }));
}
// Server Actions
'use server'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
await db.posts.create({ title, content });
revalidatePath('/blog');
}
19. React 生态工具链
19.1 构建工具
| 工具 | 说明 |
|---|---|
| Vite | 当前最流行的构建工具,极快的 HMR |
| Next.js | 全栈 React 框架,SSR/SSG/App Router |
| Remix | 全栈框架,专注 Web 标准 |
| Create React App | 已不推荐使用(已停止维护) |
19.2 项目初始化
bash
# Vite(推荐用于 SPA)
npm create vite@latest my-app -- --template react-ts
# Next.js(推荐用于全栈)
npx create-next-app@latest my-app --typescript --tailwind --app
# Remix
npx create-remix@latest my-app
19.3 常用生态库
| 类别 | 推荐库 |
|---|---|
| 状态管理 | Zustand, Redux Toolkit, Jotai |
| 数据获取 | TanStack Query, SWR |
| 路由 | React Router, TanStack Router |
| 表单 | React Hook Form + Zod |
| UI 组件库 | shadcn/ui, Ant Design, Material UI, Radix UI |
| 动画 | Framer Motion, React Spring |
| 图表 | Recharts, Visx, Nivo |
| 国际化 | react-i18next, next-intl |
| 日期 | date-fns, dayjs |
| 拖拽 | dnd-kit, @hello-pangea/dnd |
| 表格 | TanStack Table |
| 虚拟列表 | react-window, react-virtuoso |
| 测试 | Vitest, React Testing Library, Playwright |
| 代码规范 | ESLint, Prettier, Biome |
20. 项目最佳实践
20.1 目录结构推荐
src/
├── app/ # 页面/路由(Next.js App Router)
├── components/ # 共享组件
│ ├── ui/ # 基础 UI 组件(Button, Input, Modal...)
│ └── layout/ # 布局组件(Header, Footer, Sidebar...)
├── features/ # 按功能模块组织
│ ├── auth/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── services/
│ │ └── types.ts
│ └── dashboard/
│ ├── components/
│ ├── hooks/
│ └── utils/
├── hooks/ # 全局自定义 Hooks
├── lib/ # 第三方库封装、工具函数
├── services/ # API 请求层
├── stores/ # 全局状态(Zustand store)
├── types/ # 全局 TypeScript 类型
├── utils/ # 工具函数
└── styles/ # 全局样式
20.2 代码规范
jsx
// ✅ 组件文件命名:PascalCase
// UserProfile.tsx, UserCard.tsx
// ✅ Hook 文件命名:camelCase,以 use 开头
// useAuth.ts, useFetch.ts
// ✅ 工具函数:camelCase
// formatDate.ts, calculatePrice.ts
// ✅ 常量:UPPER_SNAKE_CASE
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = '/api/v1';
// ✅ 类型/接口:PascalCase
interface UserProfile {
id: string;
name: string;
}
// ✅ 组件 Props 接口命名
interface ButtonProps { /* ... */ }
interface UserCardProps { /* ... */ }
// ✅ 提前返回,减少嵌套
function UserProfile({ userId }) {
const { data, isLoading, error } = useFetch(`/users/${userId}`);
if (isLoading) return <Skeleton />;
if (error) return <ErrorMessage error={error} />;
if (!data) return null;
return <div>{data.name}</div>; // 主逻辑
}
20.3 常见错误与避免
jsx
// ❌ 错误 1: 直接修改状态
const [items, setItems] = useState([1, 2, 3]);
// items.push(4); // ❌
setItems([...items, 4]); // ✅
// ❌ 错误 2: useEffect 缺少依赖
useEffect(() => {
fetchData(userId); // userId 必须在依赖数组中
}, []); // ❌ 缺少 userId
// ✅ 正确:[userId]
// ❌ 错误 3: 在渲染中创建新对象/函数导致子组件重渲染
function Parent() {
// ❌ 每次渲染都是新对象
return <Child style={{ color: 'red' }} onClick={() => {}} />;
}
// ✅ 使用 useMemo/useCallback 或提取为常量
// ❌ 错误 4: 在 useEffect 中无限循环
const [data, setData] = useState([]);
useEffect(() => {
setData([...data, newItem]); // data 变化 → 触发 effect → 又设置 data
}, [data]); // ❌ 无限循环
// ✅ 使用函数式更新:setData(prev => [...prev, newItem])
// ❌ 错误 5: 用 index 作为 key(列表会增删排序时)
{items.map((item, i) => <Item key={i} />)} // ❌
{items.map(item => <Item key={item.id} />)} // ✅
// ❌ 错误 6: 过度使用 useEffect
// 很多场景(计算派生数据、事件处理、渲染逻辑)不需要 useEffect
// ✅ 派生数据直接在渲染中计算
const fullName = `${firstName} ${lastName}`; // 不需要 useEffect + useState
20.4 可访问性(Accessibility)
jsx
// 使用语义化 HTML
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
// 为图片添加 alt
<img src="photo.jpg" alt="A sunset over the mountains" />
<img src="decorative.jpg" alt="" /> {/* 装饰性图片用空 alt */}
// 表单关联 label
<label htmlFor="email">Email</label>
<input id="email" type="email" aria-describedby="email-hint" />
<span id="email-hint">We'll never share your email.</span>
// 按钮使用语义化标签
<button onClick={handleClose} aria-label="Close dialog">×</button>
// 管理焦点
const dialogRef = useRef();
useEffect(() => {
dialogRef.current?.focus();
}, []);
附录:快速参考
Hooks 速查
| Hook | 用途 | 示例 |
|---|---|---|
| useState | 状态管理 | const [x, setX] = useState(0) |
| useEffect | 副作用 | useEffect(() => { ... }, [dep]) |
| useContext | 消费 Context | const val = useContext(MyCtx) |
| useReducer | 复杂状态 | const [s, d] = useReducer(fn, init) |
| useRef | 引用/可变值 | const ref = useRef(null) |
| useMemo | 缓存计算 | const v = useMemo(() => calc(), [dep]) |
| useCallback | 缓存函数 | const fn = useCallback(() => {}, [dep]) |
| useId | 生成唯一 ID | const id = useId() |
| useTransition | 非紧急更新 | const [p, start] = useTransition() |
| useDeferredValue | 延迟值 | const d = useDeferredValue(val) |
| useLayoutEffect | 同步 DOM 副作用 | 类似 useEffect |
| useImperativeHandle | 自定义 ref 暴露 | 配合 forwardRef |
| useSyncExternalStore | 订阅外部 store | 库作者使用 |
| useActionState | 表单 action 状态 | React 19 |
| useOptimistic | 乐观更新 | React 19 |
| use | 读取 Promise/Context | React 19 |
React 19 速查
| 特性 | 说明 |
|---|---|
| Actions | async transitions 自动管理 pending/error |
| useActionState | 表单 action 状态管理 |
| useOptimistic | 乐观 UI 更新 |
| use() | 渲染中读取 Promise/Context |
| React Compiler | 自动 memoization(实验性) |
| Server Components | 服务端渲染组件,零客户端 JS |
| Server Actions | 直接从客户端调用服务端函数 |
| ref as prop | 函数组件直接接收 ref,无需 forwardRef |
| ref cleanup | ref 回调可返回清理函数 |
| 文档元数据 | 组件内直接用 <title>, <meta> |
| 资源预加载 | prefetchDNS, preconnect, preload 等 API |
持续学习资源
- React 官方文档:https://react.dev
- React GitHub:https://github.com/facebook/react
- Next.js 文档:https://nextjs.org/docs
- TanStack:https://tanstack.com
- Zustand:https://zustand-demo.pmnd.rs
- React TypeScript Cheatsheet:https://react-typescript-cheatsheet.netlify.app