React
一、JSX 语法
1. JSX 是什么?本质是什么?
定义: JSX(JavaScript XML)是 JavaScript 的一种语法扩展,允许在 JavaScript 代码中编写类似 HTML 的标记语法。
原理:
- JSX 本身不能被浏览器直接识别,需要通过 Babel 等编译器转换为普通的 JavaScript 代码
- 转换后的代码实际上是
React.createElement()函数调用 - JSX 是语法糖,不是模板引擎,最终会生成虚拟 DOM 对象
代码示例:
// JSX 语法
const element = (
<div className="container">
<h1>Hello, {name}</h1>
<p>Welcome to React</p>
</div>
);
// Babel 编译后的等价代码
const element = React.createElement(
'div',
{ className: 'container' },
React.createElement('h1', null, `Hello, ${name}`),
React.createElement('p', null, 'Welcome to React')
);
JSX 生成真实 DOM 的完整流程:
JSX 语法
↓ Babel 编译
React.createElement() 调用
↓ 执行
虚拟 DOM 对象(JavaScript 对象)
↓ ReactDOM.render()
真实 DOM 节点
JSX 编译规则:
- 小写字母开头的标签:编译为原生 DOM 元素(如
div,span) - 大写字母开头的标签:编译为自定义组件(如
<MyComponent />)
常见误区:
- ❌ JSX 是模板引擎 → ✅ JSX 是 JavaScript 的语法扩展
- ❌ JSX 只能在 React 中使用 → ✅ JSX 可以被其他库使用,但 React 最常见
- ❌ JSX 会被浏览器直接解析 → ✅ JSX 必须经过编译才能被浏览器执行
2. JSX 与原生 JS 的区别
对比表格:
| 特性 | JSX | 原生 JS |
|---|---|---|
| 语法 | 类似 HTML 的声明式语法 | 函数调用的命令式语法 |
| 可读性 | 高,结构清晰 | 低,嵌套复杂 |
| 编译 | 需要 Babel 转换 | 直接执行 |
| 表达式 | 使用 {} 嵌入 JS 表达式 | 直接使用变量和函数 |
| 属性名 | 使用 camelCase(如 className) | 使用原生属性名 |
代码对比:
// JSX 写法
const component = (
<div className="app">
<h1 style={{ color: 'red' }}>{title}</h1>
<button onClick={handleClick}>Click</button>
</div>
);
// 原生 JS 写法
const component = React.createElement(
'div',
{ className: 'app' },
React.createElement(
'h1',
{ style: { color: 'red' } },
title
),
React.createElement(
'button',
{ onClick: handleClick },
'Click'
)
);
3. JSX 的优点
- 声明式编程:以声明方式描述 UI,代码更直观
- 类型安全:编译时可检测错误(配合 TypeScript)
- 防止注入攻击:默认转义嵌入值,防止 XSS
- 代码提示:IDE 提供更好的智能提示
- 逻辑与视图结合:将渲染逻辑与 UI 放在一起,更易维护
4. JSX 的注意事项
- 必须有一个根元素(或使用 Fragment)
- 属性名使用 camelCase:
className代替class,htmlFor代替for - 自闭合标签必须以
/>结尾 - 表达式用
{}包裹,不能使用语句 - 样式对象:使用双大括号
{{ }},属性名用 camelCase - 注释:使用
{/* 注释内容 */}
// ✅ 正确示例
const Component = () => (
<>
{/* 这是注释 */}
<div className="container">
<h1>{user.name}</h1>
<img src={user.avatar} alt="avatar" />
<input type="text" onChange={handleChange} />
</div>
</>
);
// ❌ 错误示例
const Wrong = () => (
<div>
<p>变量: {let x = 1;}</p> // 不能使用语句
<p class="text">{text}</p> // 应该用 className
<br> // 必须自闭合
</div>
);
5. JSX 条件渲染
实现方式:
// 1. if-else 语句
function Greeting({ isLoggedIn }) {
if (isLoggedIn) {
return <h1>Welcome back!</h1>;
}
return <h1>Please sign in.</h1>;
}
// 2. 三元运算符
const element = (
<div>
{isLoggedIn ? <UserPanel /> : <LoginForm />}
</div>
);
// 3. 逻辑与运算符 &&
const element = (
<div>
{messages.length > 0 && <MessageList messages={messages} />}
</div>
);
// 4. 立即执行函数
const element = (
<div>
{(() => {
switch (status) {
case 'loading': return <Loading />;
case 'success': return <Content />;
case 'error': return <Error />;
default: return <NotFound />;
}
})()}
</div>
);
常见误区:
// ❌ 0 会被渲染出来
const List = ({ items }) => (
<div>
{items.length && <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>}
</div>
);
// 当 items.length 为 0 时,会渲染出 "0"
// ✅ 正确写法
const List = ({ items }) => (
<div>
{items.length > 0 && <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>}
</div>
);
6. JSX 列表渲染
const TodoList = ({ todos }) => (
<ul>
{todos.map((todo, index) => (
<li key={todo.id || index}>
<span>{todo.title}</span>
{todo.completed && <span className="done">✓</span>}
</li>
))}
</ul>
);
// 提取组件的写法
const TodoItem = ({ todo }) => (
<li>
<span>{todo.title}</span>
{todo.completed && <span className="done">✓</span>}
</li>
);
const TodoList = ({ todos }) => (
<ul>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
7. JSX 样式处理
// 1. 内联样式
const styles = {
container: {
display: 'flex',
justifyContent: 'center',
padding: '20px',
backgroundColor: '#f5f5f5'
},
title: {
fontSize: '24px',
fontWeight: 'bold'
}
};
const Component = () => (
<div style={styles.container}>
<h1 style={styles.title}>Hello</h1>
</div>
);
// 2. CSS 类名
import './Component.css';
const Component = () => (
<div className="container">
<h1 className="title">Hello</h1>
</div>
);
// 3. 条件类名
import classNames from 'classnames';
const Button = ({ variant, size, disabled }) => (
<button
className={classNames(
'btn',
`btn-${variant}`,
`btn-${size}`,
{ 'btn-disabled': disabled }
)}
>
Click
</button>
);
8. JSX 属性绑定
const Component = () => {
const imgSrc = '/path/to/image.jpg';
const isDisabled = true;
const linkUrl = 'https://example.com';
const customData = { id: 1, name: 'test' };
return (
<div>
{/* 字符串属性 */}
<img src={imgSrc} alt="description" />
{/* 布尔属性 */}
<button disabled={isDisabled}>Submit</button>
{/* 动态属性 */}
<a href={linkUrl} target="_blank" rel="noopener noreferrer">
Link
</a>
{/* data-* 属性 */}
<div data-testid="custom-element" data-info={JSON.stringify(customData)}>
Content
</div>
{/* 展开属性 */}
<input {...{ type: 'text', placeholder: 'Enter name' }} />
</div>
);
};
二、组件与 Props
9. React 组件是什么?
定义: React 组件是可复用的 UI 构建块,接收输入(props)并返回 React 元素。
核心特性:
- 可复用:同一组件可在多处使用
- 可组合:小组件组合成大组件
- 独立:每个组件管理自己的状态和渲染逻辑
- 单向数据流:数据从父组件流向子组件
两种定义方式:
// 1. 函数组件(推荐)
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// ES6 箭头函数
const Welcome = ({ name }) => <h1>Hello, {name}</h1>;
// 2. 类组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
10. 函数组件与类组件的区别
对比表格:
| 特性 | 函数组件 | 类组件 |
|---|---|---|
| 语法 | 简单的 JavaScript 函数 | ES6 class,需要继承 React.Component |
| 状态管理 | useState Hook | this.state + setState |
| 生命周期 | useEffect 等 Hooks | componentDidMount 等生命周期方法 |
| this 绑定 | 无 this 问题 | 需要处理 this 绑定 |
| 性能 | 稍好,无需实例化 | 需要实例化,有额外的性能开销 |
| 代码量 | 通常更少 | 相对较多 |
| 逻辑复用 | 自定义 Hooks | 高阶组件、render props |
| 推荐度 | ⭐⭐⭐⭐⭐ 推荐 | ⭐⭐ 不推荐(遗留代码) |
详细对比:
// 函数组件 - 简洁
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
// 类组件 - 繁琐
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
document.title = `Count: ${this.state.count}`;
}
componentDidUpdate() {
document.title = `Count: ${this.state.count}`;
}
handleClick() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<button onClick={this.handleClick}>
Count: {this.state.count}
</button>
);
}
}
选择策略:
- ✅ 新项目全部使用函数组件 + Hooks
- ✅ 旧项目逐步迁移类组件到函数组件
- ❌ 不再在新代码中使用类组件
11. Props 的使用
定义: Props(properties)是组件间传递数据的机制,从父组件向子组件单向传递。
使用示例:
// 子组件
function UserProfile({ name, age, email, hobbies }) {
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Email: {email}</p>
<ul>
{hobbies.map(hobby => <li key={hobby}>{hobby}</li>)}
</ul>
</div>
);
}
// 父组件
function App() {
return (
<UserProfile
name="John"
age={30}
email="john@example.com"
hobbies={['reading', 'gaming', 'coding']}
/>
);
}
Props 默认值:
// 方式 1:defaultProps(类组件和函数组件)
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
Greeting.defaultProps = {
name: 'Guest'
};
// 方式 2:解构默认值(函数组件推荐)
function Greeting({ name = 'Guest' }) {
return <h1>Hello, {name}!</h1>;
}
Props 验证(PropTypes):
import PropTypes from 'prop-types';
function UserCard({ name, age, email, isActive }) {
return (
<div className={isActive ? 'active' : 'inactive'}>
<h3>{name}</h3>
<p>Age: {age}</p>
<p>Email: {email}</p>
</div>
);
}
UserCard.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
email: PropTypes.string.isRequired,
isActive: PropTypes.bool
};
UserCard.defaultProps = {
age: 0,
isActive: false
};
12. Props 只读性
核心原则: Props 是只读的,组件不能修改自身的 props。
原理:
- React 遵循单向数据流,数据从父组件流向子组件
- 子组件只能通过回调函数通知父组件更新数据
- 直接修改 props 会导致不可预测的行为
// ❌ 错误:直接修改 props
function BadComponent({ count }) {
count = count + 1; // 无效且会导致错误
return <p>{count}</p>;
}
// ✅ 正确:通过 state 管理可变数据
function GoodComponent({ initialCount }) {
const [count, setCount] = useState(initialCount);
return (
<p>
{count}
<button onClick={() => setCount(count + 1)}>Increment</button>
</p>
);
}
13. 组件通信方式
完整通信方案对比:
| 通信方式 | 适用场景 | 复杂度 |
|---|---|---|
| Props + 回调 | 父子组件 | 简单 |
| Context API | 跨层级组件 | 中等 |
| 状态管理(Redux) | 全局状态 | 复杂 |
| 事件总线 | 任意组件 | 中等 |
| Refs | 直接操作子组件 | 谨慎使用 |
父子组件通信
// 父传子:通过 Props
function Parent() {
const [message, setMessage] = useState('Hello from Parent');
return <Child message={message} />;
}
function Child({ message }) {
return <p>{message}</p>;
}
// 子传父:通过回调函数
function Parent() {
const [childData, setChildData] = useState('');
const handleChildData = (data) => {
setChildData(data);
};
return (
<div>
<p>From Child: {childData}</p>
<Child onSendData={handleChildData} />
</div>
);
}
function Child({ onSendData }) {
const sendData = () => {
onSendData('Hello from Child');
};
return <button onClick={sendData}>Send to Parent</button>;
}
兄弟组件通信
// 通过父组件状态中转
function Parent() {
const [sharedData, setSharedData] = useState('');
return (
<div>
<SiblingA onDataChange={setSharedData} />
<SiblingB data={sharedData} />
</div>
);
}
function SiblingA({ onDataChange }) {
return (
<input
onChange={(e) => onDataChange(e.target.value)}
placeholder="Type here"
/>
);
}
function SiblingB({ data }) {
return <p>Sibling A typed: {data}</p>;
}
跨层级组件通信(Context)
// 创建 Context
const ThemeContext = React.createContext('light');
// 提供值
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<ComponentA />
</ThemeContext.Provider>
);
}
// 消费值
function ComponentC() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<div className={theme}>
<p>Current theme: {theme}</p>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</div>
);
}
三、State 与生命周期
14. State 是什么?与 Props 的区别
定义: State 是组件内部的可变数据,用于管理组件的动态行为和 UI 状态。
对比表格:
| 特性 | State | Props |
|---|---|---|
| 来源 | 组件内部定义 | 由父组件传递 |
| 可变性 | 组件自身可修改 | 只读,不可修改 |
| 作用域 | 组件内部 | 从父到子传递 |
| 修改方式 | setState / useState | 父组件重新渲染时更新 |
| 默认值 | 组件初始化时设置 | 父组件传递或 defaultProps |
| 控制权 | 组件自身 | 父组件 |
代码对比:
// State 示例
function Counter() {
const [count, setCount] = useState(0); // 内部状态
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
// Props 示例
function Display({ value }) { // 外部传入
return <p>Value: {value}</p>;
}
15. setState 的原理与使用注意事项
原理:
setState是异步的(在 React 控制的事件处理中)- React 会将多次
setState调用批量合并 - 状态更新后触发组件重新渲染
- 使用虚拟 DOM diff 算法计算最小更新
同步与异步场景:
function Example() {
const [count, setCount] = useState(0);
// 异步场景:React 控制的事件处理中
const handleClick = () => {
setCount(count + 1);
console.log(count); // 输出旧值 0
};
// 同步场景:原生事件、setTimeout 中
useEffect(() => {
const timer = setTimeout(() => {
setCount(count + 1);
console.log(count); // React 18 后也是批量更新
}, 1000);
return () => clearTimeout(timer);
}, [count]);
return <button onClick={handleClick}>{count}</button>;
}
React 18 的变化:Automatic Batching
- React 18 之前:只在 React 事件处理中批量更新
- React 18 之后:所有场景(setTimeout、Promise、原生事件)都批量更新
- 如需同步更新:使用
flushSync
import { flushSync } from 'react-dom';
const handleClick = () => {
flushSync(() => {
setCount(count + 1);
});
console.log(count); // 输出新值
};
setState 合并机制:
// 对象合并:后面的覆盖前面的
const handleClick = () => {
setState({ a: 1 });
setState({ b: 2 });
// 结果:{ a: 1, b: 2 }(在批量更新中)
};
// 函数式更新:避免依赖旧状态的问题
const handleClick = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// 结果:count + 3
};
setState 回调:
// 类组件中 setState 的回调
this.setState({ count: 1 }, () => {
console.log('State updated:', this.state.count);
});
// 函数组件中使用 useEffect 替代
useEffect(() => {
console.log('Count updated:', count);
}, [count]);
16. React 生命周期(类组件)
生命周期流程图:
挂载阶段(Mount)
↓
constructor()
↓
getDerivedStateFromProps()
↓
render()
↓
componentDidMount()
↓
更新阶段(Update)
↓
getDerivedStateFromProps()
↓
shouldComponentUpdate()
↓
render()
↓
getSnapshotBeforeUpdate()
↓
componentDidUpdate()
↓
卸载阶段(Unmount)
↓
componentWillUnmount()
挂载阶段
class Component extends React.Component {
// 1. 构造函数:初始化 state 和绑定方法
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this);
}
// 2. 静态方法:根据 props 更新 state
static getDerivedStateFromProps(props, state) {
if (props.value !== state.prevValue) {
return { prevValue: props.value, count: 0 };
}
return null; // 不需要更新 state
}
// 3. render:必须实现,返回 React 元素
render() {
return <div>{this.state.count}</div>;
}
// 4. 组件挂载完成:发送请求、订阅、操作 DOM
componentDidMount() {
console.log('Component mounted');
this.fetchData();
}
}
更新阶段
class Component extends React.Component {
// 1. 性能优化:返回 false 阻止渲染
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.id === this.props.id) {
return false; // props.id 未变化,不更新
}
return true;
}
// 2. 渲染前获取 DOM 快照
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.list.length < this.props.list.length) {
// 记录滚动位置
return this.listRef.current.scrollTop;
}
return null;
}
// 3. 更新完成后执行:接收 snapshot 参数
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
// 恢复滚动位置
this.listRef.current.scrollTop = snapshot;
}
}
}
卸载阶段
class Component extends React.Component {
componentWillUnmount() {
// 清理工作
clearInterval(this.timer);
this.subscription.unsubscribe();
window.removeEventListener('resize', this.handleResize);
}
}
React 16.3 前后生命周期变化:
| 废弃方法 | 替代方案 |
|---|---|
| componentWillMount | componentDidMount 或 constructor |
| componentWillReceiveProps | getDerivedStateFromProps |
| componentWillUpdate | getSnapshotBeforeUpdate |
17. Hooks 与生命周期的对应关系
// useEffect 对应生命周期
useEffect(() => {
// componentDidMount 和 componentDidUpdate
console.log('Component mounted/updated');
return () => {
// componentWillUnmount
console.log('Component will unmount');
};
}, []); // 空数组:只执行一次(相当于 componentDidMount)
// 对应关系表格
// componentDidMount → useEffect(() => {}, [])
// componentDidUpdate → useEffect(() => {}, [dependency])
// componentWillUnmount → useEffect(() => { return cleanup }, [])
四、事件处理
18. React 事件处理
定义: React 实现了跨浏览器的合成事件系统(SyntheticEvent),提供一致的 API。
特点:
- 事件名使用 camelCase(如
onClick) - 事件处理函数是函数引用,不是字符串
- 使用
preventDefault()阻止默认行为,不能返回 false
function EventExample() {
const handleClick = (e) => {
e.preventDefault(); // 阻止默认行为
console.log('Clicked!');
};
return (
<div>
{/* 正确写法 */}
<button onClick={handleClick}>Click Me</button>
{/* 错误写法 */}
{/* <button onclick="handleClick()">Click</button> */}
</div>
);
}
19. 合成事件与原生事件的区别
对比表格:
| 特性 | 合成事件(SyntheticEvent) | 原生事件 |
|---|---|---|
| 兼容性 | 跨浏览器一致 | 不同浏览器可能不同 |
| 事件委托 | 统一委托到 document | 直接绑定到元素 |
| 性能 | 事件复用,减少内存 | 每个事件独立创建 |
| API | 遵循 W3C 规范 | 遵循浏览器实现 |
| 事件对象 | SyntheticEvent | 原生 Event |
| 异步访问 | React 17 前需要 persist() | 始终可用 |
事件委托机制:
DOM 树:
document
└── div#root
└── div.app
└── button (onClick)
React 事件绑定:
所有事件统一绑定到 document 或 root 容器
事件触发时,React 根据冒泡机制分发到对应组件
20. 事件传参
// 方式 1:箭头函数
function List({ items }) {
const handleClick = (id) => {
console.log('Delete item:', id);
};
return (
<ul>
{items.map(item => (
<li key={item.id}>
<button onClick={() => handleClick(item.id)}>
Delete
</button>
</li>
))}
</ul>
);
}
// 方式 2:bind 方法
function List({ items }) {
const handleClick = (id, e) => {
console.log('Delete item:', id);
};
return (
<ul>
{items.map(item => (
<li key={item.id}>
<button onClick={handleClick.bind(this, item.id)}>
Delete
</button>
</li>
))}
</ul>
);
}
// 方式 3:data 属性
function List({ items }) {
const handleClick = (e) => {
const id = e.currentTarget.dataset.id;
console.log('Delete item:', id);
};
return (
<ul>
{items.map(item => (
<li key={item.id}>
<button
onClick={handleClick}
data-id={item.id}
>
Delete
</button>
</li>
))}
</ul>
);
}
21. 事件池(Event Pooling)
说明: React 17 已移除了事件池,此问题主要针对 React 16 及更早版本。
React 16 的问题:
// React 16 中,事件对象会被复用
function handleClick(e) {
console.log(e.target); // 可用
setTimeout(() => {
console.log(e.target); // undefined!事件对象已被回收
}, 0);
}
// 解决方案 1:persist()
function handleClick(e) {
e.persist();
setTimeout(() => {
console.log(e.target); // 可用
}, 0);
}
// 解决方案 2:提取需要的值
function handleClick(e) {
const target = e.target;
setTimeout(() => {
console.log(target); // 可用
}, 0);
}
五、列表与 Keys
22. Key 的作用与最佳实践
定义: key 是 React 用于识别列表中哪些元素发生变化的特殊属性。
核心作用:
- 帮助 Diff 算法高效识别元素
- 保持组件状态与元素对应
- 避免不必要的重新渲染
// ✅ 正确:使用唯一 ID
const TodoList = ({ todos }) => (
<ul>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</ul>
);
// ❌ 错误:使用 index 作为 key
const TodoList = ({ todos }) => (
<ul>
{todos.map((todo, index) => (
<TodoItem key={index} todo={todo} />
))}
</ul>
);
为什么不能随机数作为 key?
// ❌ 错误:每次渲染都生成新的 key
const List = ({ items }) => (
<ul>
{items.map(item => (
<li key={Math.random()}>{item.name}</li>
))}
</ul>
);
// 导致每次渲染都重新创建所有 DOM 节点
23. Index 作为 Key 的问题
问题场景:
// 初始渲染
const items = ['A', 'B', 'C'];
// key=0: A, key=1: B, key=2: C
// 在开头插入 'X' 后
const items = ['X', 'A', 'B', 'C'];
// key=0: X, key=1: A, key=2: B, key=3: C
问题分析:
- 所有 key 都发生了变化,导致全部重新渲染
- 如果有输入框等状态组件,状态会错乱
- 性能下降,失去了 key 的优化作用
何时可以使用 Index?
- 列表是静态的,不会发生变化
- 列表项没有输入框等状态
- 仅作为展示,不需要维护状态
六、表单处理
24. 受控组件
定义: 表单元素的值由 React state 控制,每次输入都触发 state 更新。
function ControlledForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
agree: false
});
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form data:', formData);
};
return (
<form onSubmit={handleSubmit}>
<input
name="username"
value={formData.username}
onChange={handleChange}
placeholder="Username"
/>
<input
name="email"
type="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
/>
<label>
<input
name="agree"
type="checkbox"
checked={formData.agree}
onChange={handleChange}
/>
I agree to the terms
</label>
<button type="submit">Submit</button>
</form>
);
}
25. 非受控组件
定义: 表单元素的值由 DOM 自身管理,通过 ref 获取值。
function UncontrolledForm() {
const nameRef = useRef(null);
const emailRef = useRef(null);
const fileInputRef = useRef(null);
const handleSubmit = (e) => {
e.preventDefault();
console.log('Name:', nameRef.current.value);
console.log('Email:', emailRef.current.value);
console.log('File:', fileInputRef.current.files[0]);
};
return (
<form onSubmit={handleSubmit}>
<input ref={nameRef} defaultValue="John" placeholder="Name" />
<input ref={emailRef} type="email" placeholder="Email" />
<input ref={fileInputRef} type="file" />
<button type="submit">Submit</button>
</form>
);
}
26. 受控组件与非受控组件对比
对比表格:
| 特性 | 受控组件 | 非受控组件 |
|---|---|---|
| 数据源 | React state | DOM 自身 |
| 值更新 | 每次输入都更新 state | DOM 内部管理 |
| 获取值 | 直接读 state | 通过 ref 获取 |
| 即时验证 | 容易实现 | 较难实现 |
| 性能 | 频繁渲染可能影响性能 | 性能更好 |
| 适用场景 | 需要即时验证、禁用提交按钮 | 简单表单、文件上传 |
选择策略:
- 大多数表单使用受控组件
- 文件上传必须使用非受控组件
- 简单表单可使用非受控组件减少渲染
27. 表单验证
function ValidatedForm() {
const [formData, setFormData] = useState({
email: '',
password: ''
});
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (!formData.email) {
newErrors.email = 'Email is required';
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = 'Email is invalid';
}
if (!formData.password) {
newErrors.password = 'Password is required';
} else if (formData.password.length < 6) {
newErrors.password = 'Password must be at least 6 characters';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
console.log('Form submitted:', formData);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
name="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
placeholder="Email"
/>
{errors.email && <span className="error">{errors.email}</span>}
</div>
<div>
<input
name="password"
type="password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
placeholder="Password"
/>
{errors.password && <span className="error">{errors.password}</span>}
</div>
<button type="submit">Submit</button>
</form>
);
}
七、Context API
28. Context API 使用
定义: Context 提供了跨组件传递数据的方式,无需逐层传递 props。
创建和使用:
// 1. 创建 Context
const ThemeContext = React.createContext('light');
// 2. 提供值(Provider)
function App() {
const [theme, setTheme] = useState('dark');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
// 3. 消费值 - 方式 1:useContext Hook(推荐)
function ThemedButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
className={theme}
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
Toggle Theme
</button>
);
}
// 4. 消费值 - 方式 2:Consumer 组件
function ThemedButton() {
return (
<ThemeContext.Consumer>
{({ theme, setTheme }) => (
<button
className={theme}
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
// 5. 消费值 - 方式 3:classType.contextType
class ThemedButton extends React.Component {
static contextType = ThemeContext;
render() {
const { theme, setTheme } = this.context;
return <button className={theme}>Toggle</button>;
}
}
29. Context 的应用场景
适用场景:
- 主题切换(Theme)
- 用户认证信息(User/Auth)
- 语言国际化(i18n)
- 全局配置
不适用场景:
- 高频更新的状态(会导致所有消费者重新渲染)
- 组件间频繁交互的数据
30. Context 的性能问题与优化
问题: Context value 变化时,所有消费者组件都会重新渲染。
优化方案:
// 1. 拆分 Context
const ThemeContext = React.createContext();
const UserContext = React.createContext();
// 2. 使用 useMemo 避免不必要的更新
function App() {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState(null);
const themeValue = useMemo(() => ({ theme, setTheme }), [theme]);
const userValue = useMemo(() => ({ user, setUser }), [user]);
return (
<ThemeContext.Provider value={themeValue}>
<UserContext.Provider value={userValue}>
<Content />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
// 3. 使用 Context Selector 模式
function useThemeSelector(selector) {
const context = useContext(ThemeContext);
return selector(context);
}
function ThemedText() {
const theme = useThemeSelector(ctx => ctx.theme);
return <p className={theme}>Hello</p>;
}
八、React Router
31. React Router 基础
定义: React Router 是 React 生态中最流行的路由库,支持声明式路由导航。
核心组件:
import { BrowserRouter, Routes, Route, Link, Navigate } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/users/123">User</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:id" element={<User />} />
<Route path="/protected" element={<ProtectedRoute />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
);
}
32. BrowserRouter 与 HashRouter 对比
对比表格:
| 特性 | BrowserRouter | HashRouter |
|---|---|---|
| URL 格式 | /about | /#/about |
| History API | 使用 HTML5 History API | 使用 URL hash |
| 服务器配置 | 需要配置 | 不需要 |
| SEO | 友好 | 不友好 |
| 兼容性 | IE9+ | 所有浏览器 |
| 适用场景 | 生产环境 | 静态文件托管 |
33. 路由传参
// 1. 动态路由参数
// 定义
<Route path="/users/:id" element={<User />} />
// 使用
function User() {
const { id } = useParams();
return <h2>User ID: {id}</h2>;
}
// 导航
<Link to={`/users/${userId}`}>View User</Link>
// 2. 查询参数(Search Params)
// 导航
<Link to="/search?keyword=react&page=1">Search</Link>
// 使用
function Search() {
const [searchParams] = useSearchParams();
const keyword = searchParams.get('keyword');
const page = searchParams.get('page');
return <p>Searching for: {keyword}</p>;
}
// 3. 状态参数(State)
// 导航
navigate('/detail', { state: { from: 'home', data: item } });
// 使用
function Detail() {
const location = useLocation();
const { from, data } = location.state || {};
return <p>Came from: {from}</p>;
}
34. 路由守卫
// 认证路由守卫
function ProtectedRoute({ children }) {
const { isAuthenticated } = useAuth();
const location = useLocation();
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// 使用
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
35. React Router v5 到 v6 的变化
主要变化:
| v5 | v6 |
|---|---|
<Switch> | <Routes> |
<Route component={X} /> | <Route element={<X />} /> |
useHistory() | useNavigate() |
history.push() | navigate() |
exact prop | 默认精确匹配 |
<Route path=":id"> | 保持不变 |
| 嵌套路由复杂 | 原生支持嵌套路由 |
// v5
<Switch>
<Route exact path="/" component={Home} />
<Route path="/users/:id" component={User} />
</Switch>
const history = useHistory();
history.push('/about');
// v6
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users/:id" element={<User />} />
</Routes>
const navigate = useNavigate();
navigate('/about');
九、状态管理(Redux、MobX)
36. Redux 核心概念
定义: Redux 是一个可预测的状态管理容器,基于 Flux 架构思想。
三大原则:
- 单一数据源:整个应用的 state 存储在一棵 object tree 中
- State 是只读的:唯一改变 state 的方法是触发 action
- 使用纯函数执行修改:reducers 是纯函数
核心概念:
Action:描述发生了什么的普通对象
↓
Dispatch:触发 action 的方法
↓
Reducer:纯函数,根据 action 更新 state
↓
Store:存储 state 的容器
↓
Subscribe:监听 state 变化
↓
View:根据 state 渲染 UI
37. Redux 工作流程
// 1. 定义 Action Types
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';
// 2. 创建 Actions
const addTodo = (text) => ({
type: ADD_TODO,
payload: { id: Date.now(), text, completed: false }
});
// 3. 创建 Reducer
const initialState = { todos: [] };
function todoReducer(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return {
...state,
todos: [...state.todos, action.payload]
};
case TOGGLE_TODO:
return {
...state,
todos: state.todos.map(todo =>
todo.id === action.payload.id
? { ...todo, completed: !todo.completed }
: todo
)
};
default:
return state;
}
}
// 4. 创建 Store
import { configureStore } from '@reduxjs/toolkit';
const store = configureStore({
reducer: {
todos: todoReducer
}
});
// 5. 在组件中使用
import { useSelector, useDispatch } from 'react-redux';
function TodoList() {
const todos = useSelector(state => state.todos.todos);
const dispatch = useDispatch();
return (
<div>
{todos.map(todo => (
<div key={todo.id}>
<span>{todo.text}</span>
<button onClick={() => dispatch(addTodo('New Todo'))}>
Add
</button>
</div>
))}
</div>
);
}
38. Redux 中间件
定义: 中间件位于 dispatch 和 reducer 之间,用于处理副作用。
常用中间件:
| 中间件 | 用途 | 特点 |
|---|---|---|
| redux-thunk | 处理异步 action | 简单,适合简单异步 |
| redux-saga | 处理复杂异步流程 | 强大,使用 Generator |
| redux-observable | 响应式异步处理 | 使用 RxJS |
Redux Thunk 示例:
// Action Creator 返回函数
const fetchUser = (userId) => {
return async (dispatch, getState) => {
dispatch({ type: 'FETCH_USER_REQUEST' });
try {
const response = await fetch(`/api/users/${userId}`);
const user = await response.json();
dispatch({ type: 'FETCH_USER_SUCCESS', payload: user });
} catch (error) {
dispatch({ type: 'FETCH_USER_FAILURE', payload: error.message });
}
};
};
// 使用
dispatch(fetchUser(123));
Redux Saga 示例:
import { call, put, takeLatest } from 'redux-saga/effects';
function* fetchUserSaga(action) {
try {
const user = yield call(fetch, `/api/users/${action.payload}`);
yield put({ type: 'FETCH_USER_SUCCESS', payload: user });
} catch (error) {
yield put({ type: 'FETCH_USER_FAILURE', payload: error.message });
}
}
function* watchFetchUser() {
yield takeLatest('FETCH_USER_REQUEST', fetchUserSaga);
}
39. Redux Toolkit(推荐用法)
import { createSlice, configureStore } from '@reduxjs/toolkit';
// 创建 slice
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
state.value += 1; // 使用 immer,可以"修改"state
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
}
}
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
// 创建 store
const store = configureStore({
reducer: {
counter: counterSlice.reducer
}
});
40. MobX 与 Redux 对比
对比表格:
| 特性 | Redux | MobX |
|---|---|---|
| 编程范式 | 命令式、函数式 | 声明式、响应式 |
| 数据流 | 单向数据流 | 响应式数据流 |
| 状态修改 | 通过 dispatch action | 直接修改 observable |
| 学习曲线 | 较陡峭 | 平缓 |
| 样板代码 | 较多 | 较少 |
| DevTools | 强大 | 良好 |
| 适用场景 | 大型项目、复杂状态 | 中小型项目、简单状态 |
| 性能优化 | 手动优化 | 自动追踪依赖 |
MobX 示例:
import { makeObservable, observable, action, computed } from 'mobx';
import { observer } from 'mobx-react-lite';
class TodoStore {
todos = [];
constructor() {
makeObservable(this, {
todos: observable,
addTodo: action,
toggleTodo: action,
completedCount: computed
});
}
addTodo(text) {
this.todos.push({ id: Date.now(), text, completed: false });
}
toggleTodo(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) todo.completed = !todo.completed;
}
get completedCount() {
return this.todos.filter(t => t.completed).length;
}
}
const todoStore = new TodoStore();
const TodoList = observer(() => (
<div>
<button onClick={() => todoStore.addTodo('New Todo')}>Add</button>
<p>Completed: {todoStore.completedCount}</p>
</div>
));
十、React Hooks
41. useState
定义: useState 是 React 最常用的 Hook,用于在函数组件中添加状态。
// 基本用法
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
// 复杂状态
function UserForm() {
const [form, setForm] = useState({
name: '',
email: '',
age: 0
});
const handleChange = (e) => {
const { name, value } = e.target;
setForm(prev => ({
...prev,
[name]: value
}));
};
return (
<form>
<input name="name" value={form.name} onChange={handleChange} />
<input name="email" value={form.email} onChange={handleChange} />
</form>
);
}
// 函数式初始化(只执行一次)
function ExpensiveComponent({ initialId }) {
const [data, setData] = useState(() => {
return computeExpensiveValue(initialId);
});
}
常见误区:
// ❌ 错误:直接修改 state
const [arr, setArr] = useState([]);
arr.push(1); // 不会触发重新渲染
// ✅ 正确:创建新对象
setArr([...arr, 1]);
setArr(prev => [...prev, 1]);
42. useEffect
定义: useEffect 用于处理副作用(数据获取、订阅、手动 DOM 操作等)。
// 1. 无依赖数组:每次渲染后执行
useEffect(() => {
console.log('每次渲染后执行');
});
// 2. 空依赖数组:只在挂载和卸载时执行
useEffect(() => {
console.log('挂载时执行');
return () => {
console.log('卸载时执行');
};
}, []);
// 3. 有依赖数组:依赖变化时执行
useEffect(() => {
console.log('count 变化时执行:', count);
}, [count]);
// 4. 异步数据获取
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let isMounted = true; // 防止内存泄漏
const fetchUser = async () => {
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (isMounted) {
setUser(data);
}
} catch (error) {
console.error('Failed to fetch user:', error);
}
};
fetchUser();
return () => {
isMounted = false; // 清理函数
};
}, [userId]);
return user ? <div>{user.name}</div> : <p>Loading...</p>;
}
常见陷阱:
// ❌ 错误:遗漏依赖
useEffect(() => {
console.log(count);
}, []); // count 变化时不会更新
// ✅ 正确:添加依赖
useEffect(() => {
console.log(count);
}, [count]);
// ❌ 错误:无限循环
useEffect(() => {
setCount(count + 1);
}); // 没有依赖数组
// ✅ 正确:使用条件
useEffect(() => {
if (count < 10) {
setCount(count + 1);
}
}, [count]);
43. useCallback 与 useMemo
定义:
useCallback:记忆函数,避免每次渲染创建新函数useMemo:记忆值,避免重复计算
// useCallback - 记忆函数
function TodoList({ todos, onToggle }) {
const handleToggle = useCallback((id) => {
onToggle(id);
}, [onToggle]);
return todos.map(todo => (
<TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
));
}
// useMemo - 记忆计算结果
function ExpensiveComponent({ items, filter }) {
const filteredItems = useMemo(() => {
console.log('Filtering items...');
return items.filter(item => item.type === filter);
}, [items, filter]);
return (
<ul>
{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
对比表格:
| Hook | 返回值 | 用途 | 何时使用 |
|---|---|---|---|
| useCallback | 函数 | 避免子组件不必要的渲染 | 传递给优化过的子组件的回调 |
| useMemo | 值 | 避免重复计算 | 计算密集型操作 |
// 等价关系
useCallback(fn, deps) === useMemo(() => fn, deps)
44. useRef
定义: useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。
用途:
- 访问 DOM 元素
- 保存不触发重新渲染的值
// 1. 访问 DOM 元素
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus</button>
</>
);
}
// 2. 保存可变值(不触发渲染)
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef(null);
const startTimer = () => {
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
};
const stopTimer = () => {
clearInterval(intervalRef.current);
};
useEffect(() => {
return () => clearInterval(intervalRef.current);
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
</div>
);
}
45. useReducer
定义: useReducer 是 useState 的替代方案,适用于复杂状态逻辑。
function todoReducer(state, action) {
switch (action.type) {
case 'ADD':
return [...state, { id: Date.now(), text: action.payload, completed: false }];
case 'TOGGLE':
return state.map(todo =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
);
case 'DELETE':
return state.filter(todo => todo.id !== action.payload);
default:
return state;
}
}
function TodoApp() {
const [todos, dispatch] = useReducer(todoReducer, []);
const [input, setInput] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (input.trim()) {
dispatch({ type: 'ADD', payload: input });
setInput('');
}
};
return (
<div>
<form onSubmit={handleSubmit}>
<input value={input} onChange={e => setInput(e.target.value)} />
<button type="submit">Add</button>
</form>
<ul>
{todos.map(todo => (
<li key={todo.id} onClick={() => dispatch({ type: 'TOGGLE', payload: todo.id })}>
{todo.text} {todo.completed && '✓'}
</li>
))}
</ul>
</div>
);
}
46. useLayoutEffect
定义: useLayoutEffect 与 useEffect 类似,但在所有 DOM 变更之后同步执行。
与 useEffect 的区别:
| 特性 | useEffect | useLayoutEffect |
|---|---|---|
| 执行时机 | 渲染后异步执行 | DOM 变更后同步执行 |
| 阻塞渲染 | 否 | 是 |
| 适用场景 | 数据获取、订阅 | 测量 DOM、同步更新 DOM |
// useLayoutEffect 用于测量 DOM
function Tooltip({ target }) {
const [position, setPosition] = useState({ top: 0, left: 0 });
const tooltipRef = useRef(null);
useLayoutEffect(() => {
if (tooltipRef.current && target) {
const rect = target.getBoundingClientRect();
setPosition({
top: rect.top - 10,
left: rect.left + rect.width / 2
});
}
}, [target]);
return (
<div ref={tooltipRef} style={{ position: 'absolute', ...position }}>
Tooltip content
</div>
);
}
47. 自定义 Hook
定义: 自定义 Hook 是提取组件间共享逻辑的函数,以 use 开头。
// 自定义 Hook:获取窗口尺寸
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
// 自定义 Hook:数据请求
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
const json = await response.json();
setData(json);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
// 使用
function UserProfile({ userId }) {
const { data, loading, error } = useFetch(`/api/users/${userId}`);
const { width } = useWindowSize();
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<div>
<h1>{data.name}</h1>
<p>Window width: {width}</p>
</div>
);
}
48. Hooks 使用规则
两条铁律:
- 只在最顶层调用 Hooks:不能在循环、条件或嵌套函数中调用
- 只在 React 函数中调用 Hooks:不能在普通 JavaScript 函数中调用
// ❌ 错误:在条件语句中
function Component({ condition }) {
if (condition) {
const [state, setState] = useState(0); // 违反规则
}
}
// ✅ 正确:在最顶层
function Component({ condition }) {
const [state, setState] = useState(0);
if (condition) {
// 使用 state
}
}
// ❌ 错误:在普通函数中
function regularFunction() {
const [state, setState] = useState(0); // 违反规则
}
// ✅ 正确:在 React 函数中
function Component() {
const [state, setState] = useState(0);
}
原理: React 通过调用顺序来关联 Hook 和组件,顺序必须保持一致。
49. React 18 新增 Hooks
// useTransition:标记非紧急更新
function Search() {
const [inputValue, setInputValue] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
setInputValue(e.target.value);
startTransition(() => {
// 非紧急更新:过滤列表
setResults(filterResults(e.target.value));
});
};
return (
<div>
<input value={inputValue} onChange={handleChange} />
{isPending && <p>Loading...</p>}
<ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>
</div>
);
}
// useDeferredValue:延迟更新值
function Search({ query }) {
const deferredQuery = useDeferredValue(query);
return (
<div>
<SearchResults query={deferredQuery} />
</div>
);
}
// useId:生成唯一 ID(用于 SSR hydration)
function Form() {
const id = useId();
return (
<div>
<label htmlFor={id}>Name</label>
<input id={id} type="text" />
</div>
);
}
// useSyncExternalStore:同步外部数据源
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 // SSR 时的默认值
);
}
十一、React 原理
50. Virtual DOM
定义: Virtual DOM 是真实 DOM 的轻量级 JavaScript 对象表示。
结构:
// JSX
<div className="app">
<h1>Hello</h1>
</div>
// Virtual DOM 对象
{
type: 'div',
props: {
className: 'app',
children: [
{
type: 'h1',
props: {
children: 'Hello'
}
}
]
}
}
优势:
- 减少 DOM 操作:批量更新,减少重排重绘
- 跨平台:可以渲染到不同平台(Web、Native、Canvas)
- 可预测:数据驱动视图
- Diff 算法优化:高效计算最小更新
工作流程:
State 变化
↓
创建新的 Virtual DOM 树
↓
与旧的 Virtual DOM 树进行 Diff
↓
计算最小 DOM 操作
↓
批量更新真实 DOM
51. Diff 算法
三大策略:
- Tree Diff:同层级比较,不跨级比较
- Component Diff:相同组件类型复用,不同组件类型替换整棵树
- Element Diff:通过 key 识别同层级元素
// Tree Diff
<div> <div>
<Header /> → <p> // 直接替换整个子树
</div> </div>
// Component Diff
<Component /> → <Component /> // 复用组件实例
<Header /> → <Footer /> // 替换组件
// Element Diff(列表)
<li key="A">A</li> <li key="B">B</li>
<li key="B">B</li> → <li key="A">A</li>
<li key="C">C</li>
// 通过 key 识别,移动 A 和 B,插入 C
Diff 算法流程:
旧节点 A 新节点 A'
↓ ↓
比较类型是否相同
↓
┌─相同─┐ ┌─不同─┐
↓ ↓
复用节点 替换节点
↓ ↓
更新 props 删除旧节点
↓ 创建新节点
比较 children
↓
遍历子节点,递归 Diff
52. Fiber 架构
定义: Fiber 是 React 16 引入的新协调引擎,实现了异步可中断的渲染。
为什么需要 Fiber?
React 15 的问题:
- 递归更新是同步的,不能中断
- 大量组件更新会导致长时间阻塞主线程
- 造成掉帧、卡顿
Fiber 的解决方案:
- 将渲染工作拆分成小单元
- 可以暂停、恢复、重用或丢弃工作
- 根据优先级调度任务
Fiber 节点结构:
function FiberNode(tag, pendingProps, key, mode) {
// 实例相关
this.tag = tag;
this.key = key;
this.type = null;
this.stateNode = null;
// Fiber 树相关
this.return = null; // 父节点
this.child = null; // 子节点
this.sibling = null; // 兄弟节点
this.index = 0;
// 更新相关
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
// 副作用相关
this.effectTag = NoEffect;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
// 优先级相关
this.expirationTime = NoWork;
this.alternate = null; // 指向另一棵树的对应节点
}
双缓冲机制:
内存中有两棵 Fiber 树:
Current Tree(当前屏幕显示的)
WorkInProgress Tree(正在构建的)
渲染完成后,指针切换:
root.current = workInProgress;
53. 时间切片与优先级调度
时间切片(Time Slicing):
主线程时间线:
|-- 5ms 渲染 --|-- 事件处理 --|-- 5ms 渲染 --|-- 用户输入响应 --|
切片1 中断 切片2 及时响应
优先级模型:
| 优先级 | 类型 | 示例 |
|---|---|---|
| Immediate | 立即 | 用户交互 |
| UserBlocking | 用户阻塞 | 输入框 |
| Normal | 普通 | 数据更新 |
| Low | 低 | 通知 |
| Idle | 空闲 | 预加载 |
54. 并发模式
定义: 并发模式允许 React 同时准备多个版本的 UI。
// startTransition 标记非紧急更新
function TabContainer() {
const [tab, setTab] = useState('home');
const [isPending, startTransition] = useTransition();
const switchTab = (newTab) => {
// 紧急更新:立即切换
setTab(newTab);
// 非紧急更新:可以中断
startTransition(() => {
renderTabContent(newTab);
});
};
return (
<div>
{isPending && <Spinner />}
<TabContent tab={tab} />
</div>
);
}
55. React 合成事件原理
事件绑定机制:
React 事件委托到 root 节点(React 17 之前是 document)
↓
事件触发时,React 创建 SyntheticEvent 对象
↓
根据组件树构建事件路径(捕获 → 目标 → 冒泡)
↓
按顺序执行事件处理器
↓
事件池回收 SyntheticEvent(React 16)
React 17 的变化:
- 事件不再委托到 document,而是委托到 root 容器
- 多个 React 版本可以共存
- 移除了事件池
十二、React 性能优化
56. React.memo
定义: React.memo 是高阶组件,用于优化函数组件的渲染。
// 默认浅比较 props
const TodoItem = React.memo(function TodoItem({ todo }) {
return <li>{todo.text}</li>;
});
// 自定义比较函数
const TodoItem = React.memo(
function TodoItem({ todo, onToggle }) {
return <li onClick={() => onToggle(todo.id)}>{todo.text}</li>;
},
(prevProps, nextProps) => {
// 返回 true 表示不需要重新渲染
return prevProps.todo.id === nextProps.todo.id &&
prevProps.todo.text === nextProps.todo.text;
}
);
57. PureComponent
定义: PureComponent 自动对 props 和 state 进行浅比较。
class TodoItem extends React.PureComponent {
render() {
return <li>{this.props.todo.text}</li>;
}
}
// 注意:只进行浅比较
// ❌ 如果父组件传递了新创建的对象/数组,仍会重新渲染
<TodoItem todo={{ ...todo, completed: !todo.completed }} />
58. 避免不必要的渲染
优化策略:
// 1. 使用 React.memo 包裹组件
const ExpensiveComponent = React.memo(({ data }) => {
return <div>{/* 复杂渲染逻辑 */}</div>;
});
// 2. 使用 useCallback 记忆回调
function Parent() {
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return <Child onClick={handleClick} />;
}
// 3. 使用 useMemo 记忆计算结果
function List({ items, filter }) {
const filteredItems = useMemo(() =>
items.filter(item => item.type === filter),
[items, filter]
);
return <ul>{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
// 4. 状态下沉(State Colocation)
// ❌ 将状态提升到不必要的父组件
function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<Modal isOpen={isOpen} />
<Content /> {/* 不需要知道 isOpen */}
</div>
);
}
// ✅ 状态下沉到需要的组件
function ModalWithTrigger() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>Open</button>
<Modal isOpen={isOpen} />
</>
);
}
59. 懒加载与代码分割
// React.lazy 配合 Suspense
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
);
}
// 路由级别的代码分割
import { lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
60. 列表优化
// 1. 正确的 key 选择
<TodoList todos={todos} />
// 使用唯一 ID 作为 key
// 2. 虚拟列表(长列表优化)
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={35}
width={300}
>
{Row}
</FixedSizeList>
);
}
// 3. 分页加载
function PaginatedList() {
const [items, setItems] = useState([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const loadMore = async () => {
if (loading) return;
setLoading(true);
const newItems = await fetchItems(page);
setItems(prev => [...prev, ...newItems]);
setPage(prev => prev + 1);
setLoading(false);
};
return (
<div onScroll={handleScroll}>
{items.map(item => <Item key={item.id} item={item} />)}
{loading && <p>Loading more...</p>}
</div>
);
}
十三、其他 React 特性
61. Refs 与 forwardRef
// 创建 Ref
function TextInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
}
// forwardRef:转发 ref 到子组件
const FancyInput = React.forwardRef((props, ref) => (
<input ref={ref} className="fancy" {...props} />
));
// 使用
function Form() {
const inputRef = useRef(null);
return (
<FancyInput ref={inputRef} placeholder="Type here" />
);
}
// useImperativeHandle:自定义暴露的方法
const FancyInput = React.forwardRef((props, ref) => {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = '';
}
}));
return <input ref={inputRef} {...props} />;
});
62. Portals
定义: Portals 将子元素渲染到父组件 DOM 层次结构之外的 DOM 节点。
import { createPortal } from 'react-dom';
function Modal({ isOpen, children }) {
if (!isOpen) return null;
return createPortal(
<div className="modal-overlay">
<div className="modal-content">
{children}
</div>
</div>,
document.getElementById('modal-root')
);
}
// 使用
function App() {
const [showModal, setShowModal] = useState(false);
return (
<div>
<button onClick={() => setShowModal(true)}>Show Modal</button>
<Modal isOpen={showModal}>
<h2>Modal Title</h2>
<p>Modal content</p>
<button onClick={() => setShowModal(false)}>Close</button>
</Modal>
</div>
);
}
适用场景:
- 模态框(Modal)
- 提示框(Tooltip)
- 下拉菜单(Dropdown)
63. Error Boundaries
定义: Error Boundaries 捕获子组件树中的 JavaScript 错误,显示备用 UI。
class ErrorBoundary extends React.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 by boundary:', error, errorInfo);
// 可以发送到错误监控服务
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div>
<h1>Something went wrong.</h1>
<details>{this.state.error.message}</details>
</div>
);
}
return this.props.children;
}
}
// 使用
function App() {
return (
<ErrorBoundary>
<MainContent />
</ErrorBoundary>
);
}
// 函数组件中使用(使用 react-error-boundary 库)
import { ErrorBoundary } from 'react-error-boundary';
function App() {
return (
<ErrorBoundary
fallback={<ErrorFallback />}
onReset={() => window.location.reload()}
>
<MainContent />
</ErrorBoundary>
);
}
64. Fragment
定义: Fragment 允许在不添加额外 DOM 节点的情况下组合子元素列表。
// 方式 1:完整语法
function Table() {
return (
<React.Fragment>
<th>Header 1</th>
<th>Header 2</th>
</React.Fragment>
);
}
// 方式 2:简写语法(推荐)
function Table() {
return (
<>
<th>Header 1</th>
<th>Header 2</th>
</>
);
}
// 带 key 的 Fragment
function Glossary({ items }) {
return (
<dl>
{items.map(item => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</React.Fragment>
))}
</dl>
);
}
65. StrictMode
定义: StrictMode 用于突出显示应用程序中的潜在问题。
检查项:
- 识别不安全的生命周期
- 警告使用旧版字符串 ref API
- 警告使用废弃的 findDOMNode API
- 检测意外的副作用
- 检测过时的 context API
- 确保可复用的 state
function App() {
return (
<React.StrictMode>
<Router>
<AppContent />
</Router>
</React.StrictMode>
);
}
十四、React 18 新特性
66. Automatic Batching
定义: React 18 自动批处理所有状态更新,包括异步回调中的更新。
// React 18 之前
setTimeout(() => {
setCount(c => c + 1); // 触发渲染
setFlag(f => !f); // 触发渲染
}, 1000);
// 两次渲染
// React 18
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
}, 1000);
// 一次渲染(自动批处理)
// 退出批处理
import { flushSync } from 'react-dom';
flushSync(() => {
setCount(c => c + 1); // 立即渲染
});
// 此时 count 已更新
67. 服务端组件(Server Components)
定义: 服务端组件在服务器上运行,不发送到客户端。
// 服务器组件(.server.js)
async function Note({ id }) {
const note = await db.notes.get(id); // 直接访问数据库
return (
<div>
<h1>{note.title}</h1>
<MarkdownRenderer source={note.body} />
</div>
);
}
// 客户端组件(标记 'use client')
'use client';
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(c => c + 1)}>
Count: {count}
</button>
);
}
优势:
- 零 bundle 大小
- 直接访问后端资源
- 自动代码分割
- 流式 SSR
十五、服务端渲染(SSR)
68. SSR 原理与优势
SSR 流程:
客户端请求
↓
服务器执行 React 组件
↓
生成 HTML 字符串
↓
发送 HTML 到客户端
↓
客户端 hydration(绑定事件)
优势:
- SEO 友好:搜索引擎可以抓取内容
- 首屏加载快:用户更快看到内容
- 社交分享:社交平台的爬虫可以获取内容
// Next.js 示例
export async function getServerSideProps(context) {
const data = await fetchData();
return { props: { data } };
}
function Page({ data }) {
return <div>{data.title}</div>;
}
export default Page;
十六、React 测试
69. React Testing Library
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('counter increments', async () => {
render(<Counter />);
const button = screen.getByRole('button', { name: /increment/i });
const count = screen.getByText(/count: 0/i);
await userEvent.click(button);
expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
});
test('form submission', async () => {
const handleSubmit = jest.fn();
render(<LoginForm onSubmit={handleSubmit} />);
await userEvent.type(screen.getByLabelText(/email/i), 'test@example.com');
await userEvent.type(screen.getByLabelText(/password/i), 'password123');
await userEvent.click(screen.getByRole('button', { name: /submit/i }));
expect(handleSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123'
});
});
十七、React TypeScript
70. 类型定义
// 组件 Props 类型
interface UserCardProps {
name: string;
age: number;
email?: string;
hobbies?: string[];
onClick?: (id: string) => void;
}
const UserCard: React.FC<UserCardProps> = ({
name,
age,
email,
hobbies = [],
onClick
}) => {
return (
<div onClick={() => onClick?.(name)}>
<h2>{name}</h2>
<p>Age: {age}</p>
</div>
);
};
// Hook 类型
function useFetch<T>(url: string): { data: T | null; loading: boolean; error: string | null } {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
// Ref 类型
function TextInput() {
const inputRef = useRef<HTMLInputElement>(null);
const focus = () => {
inputRef.current?.focus();
};
return <input ref={inputRef} />;
}
// 事件类型
function Form() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<input onChange={handleChange} />
</form>
);
}
十八、React vs Vue/Angular 对比
71. React vs Vue
对比表格:
| 特性 | React | Vue |
|---|---|---|
| 类型 | UI 库 | 渐进式框架 |
| 语法 | JSX | 模板语法 |
| 状态管理 | Redux/Context | Vuex/Pinia |
| 路由 | React Router | Vue Router |
| 响应式 | 手动更新(setState) | 自动追踪(Proxy) |
| 学习曲线 | 较陡 | 平缓 |
| 生态系统 | 大而分散 | 官方维护 |
| 性能 | 优秀 | 优秀 |
| 适用场景 | 大型应用、跨平台 | 中小型应用、快速开发 |
72. React vs Angular
| 特性 | React | Angular |
|---|---|---|
| 类型 | UI 库 | 完整框架 |
| 语言 | JavaScript/TypeScript | TypeScript |
| 数据绑定 | 单向 | 双向 |
| 学习曲线 | 中等 | 陡峭 |
| 包大小 | 小 | 大 |
| 适用场景 | 灵活组合 | 企业级应用 |
十九、高频面试题精选
73. 谈谈你对 React 的看法,以及它的工作原理
看法:
- 组件化思想:将 UI 拆分为独立可复用的组件
- 声明式编程:描述 UI 应该是什么样子,而不是如何更新
- 单向数据流:数据流动可预测,易于调试
- 生态系统丰富:大量第三方库和工具
工作原理:
用户操作 → 触发事件 → 更新 State
↓
React 检测到 State 变化
↓
创建新的 Virtual DOM 树
↓
Diff 算法对比新旧树
↓
计算最小 DOM 操作
↓
Fiber 调度器按优先级执行更新
↓
批量更新真实 DOM
74. 组件化开发在 React 中的体现与优势
体现:
- 单一职责:每个组件负责一个功能
- 可复用:组件可以在多处使用
- 可组合:小组件组合成大组件
- 独立测试:每个组件可以单独测试
优势:
- 提高开发效率
- 便于维护
- 代码复用
- 团队协作友好
75. 常见问题解决
1. setState 后立即获取不到最新值
// ❌ 错误
const handleClick = () => {
setCount(count + 1);
console.log(count); // 旧值
};
// ✅ 正确:使用 useEffect
useEffect(() => {
console.log(count); // 新值
}, [count]);
2. 子组件不更新
// 检查点:
// 1. props 是否正确传递
// 2. 是否使用了 React.memo 且比较函数有问题
// 3. state 更新是否创建了新的对象/数组
3. useEffect 无限循环
// ❌ 错误
useEffect(() => {
setCount(count + 1); // 没有依赖数组
});
// ✅ 正确
useEffect(() => {
setCount(count + 1);
}, [count]); // 添加依赖