# 1.Class生命周期
在 React 16.3 之后,部分旧生命周期方法(如 componentWillMount)被废弃,并引入了新的替代方法。
# 生命周期阶段概览
- React Class 组件的生命周期可以分为 3 个阶段:
- 挂载阶段(Mounting):组件首次被创建并插入 DOM。
- 更新阶段(Updating):组件因 props 或 state 变化而重新渲染。
- 卸载阶段(Unmounting):组件从 DOM 中移除。
此外,React 还提供了 错误处理 生命周期方法(如 componentDidCatch)。
# 一. 挂载阶段(Mounting)
# 1. constructor(props)
- 调用时机:组件初始化时调用。
- 用途:
- 初始化 state
- 绑定方法(如 this.handleClick = this.handleClick.bind(this))
- ⚠️ 注意:
- 必须调用 super(props),否则 this.props 会是 undefined。
- 不要调用 setState,否则会触发额外渲染。
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this);
}
2
3
4
5
# 2. static getDerivedStateFromProps(nextProps, prevState)(不常用)
- 调用时机:在 render() 之前调用,每次渲染都会触发(包括初始挂载和更新)。
- 用途:根据 props 的变化更新 state(如
<Modal visible={propVisible}>
)。 - ⚠️ 注意:
- 必须是 静态方法(static)。
- 避免滥用,通常可以用 useEffect 替代。
static getDerivedStateFromProps(props, state) {
if (props.userId !== state.userId) {
return { userId: props.userId }; // 返回新的 state
}
return null; // 不更新 state
}
2
3
4
5
6
# 3. render()
- 调用时机:必须实现,返回 JSX 或 null 。
- 用途:计算并返回组件的 UI。
- ⚠️ 注意:
- 必须是纯函数(不能修改 state 或 props)。
- 不能调用 setState,否则会导致无限循环。
render() {
return <div>{this.state.count}</div>;
}
2
3
# 4. componentDidMount()
- 调用时机:组件挂载到 DOM 后调用 仅执行一次。
- 用途:
- 发起 API 请求
- 添加 事件监听(如 window.addEventListener)
- 操作 DOM(如 document.getElementById)
- ⚠️ 注意:
- 可以安全调用 setState,但会触发额外渲染(通常用于数据加载)
componentDidMount() {
fetch('/api/data').then(res => this.setState({ data: res.data }));
window.addEventListener('resize', this.handleResize);
}
2
3
4
# 二. 更新阶段(Updating)
- 触发更新的条件:
- props 变化
- state 变化(this.setState)
- 父组件重新渲染(即使 props 没变)
# 1. static getDerivedStateFromProps(nextProps, prevState)
- 同挂载阶段,在 props 变化时更新 state。
替代了被废弃的 componentWillReceiveProps
,设计目的是使组件的行为更加可预测。
static getDerivedStateFromProps(nextProps, prevState) {
// 根据 nextProps 和 prevState 返回新的 state 或 null
if (nextProps.value !== prevState.value) {
return { value: nextProps.value }; // 更新 state
}
return null; // 不更新 state
}
2
3
4
5
6
7
- 返回值:
- 对象:合并到当前 state 中(类似 setState)。
- null:不更新 state。
使用场景-Props 变化时同步 State
当组件的 state 需要依赖 props 的变化时(受控组件常见)。
class Input extends React.Component {
state = { value: this.props.defaultValue };
static getDerivedStateFromProps(nextProps, prevState) {
// 当父组件传入的 defaultValue 变化时,重置 value
if (nextProps.defaultValue !== prevState.prevDefaultValue) {
return {
value: nextProps.defaultValue,
prevDefaultValue: nextProps.defaultValue, // 记录上一次的 props
};
}
return null;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
使用场景-条件性更新 State
基于 props 的某些条件计算 state。
class ScrollPosition extends React.Component {
state = { shouldFix: false };
static getDerivedStateFromProps(nextProps, prevState) {
// 当滚动位置超过阈值时,固定组件
if (nextProps.scrollY > 100 && !prevState.shouldFix) {
return { shouldFix: true };
}
return null;
}
}
2
3
4
5
6
7
8
9
10
常见问题
- 为什么 getDerivedStateFromProps 是静态方法?
- 避免开发者误用 this 或执行副作用,确保逻辑纯净。
- 何时使用 getDerivedStateFromProps?
- 仅当 state 需要无条件或有条件地根据 props 更新时(如表单重置、滚动位置计算)。
- 为什么推荐用 key 重置组件?
- key 变化时,React 会销毁并重建组件,比手动同步 state 更可靠。
# 2. shouldComponentUpdate(nextProps, nextState)
- 调用时机:在 render() 之前调用。
- 用途:优化性能,决定是否重新渲染,跳过后续生命周期(render、componentDidUpdate 等)
- 默认返回 true,可以手动比较 props/state 避免不必要的渲染。
- ⚠️ 注意:
- 不要深度比较(如 JSON.stringify),否则性能更差。
- 可以使用 React.PureComponent 自动浅比较 props/state。
shouldComponentUpdate(nextProps, nextState) {
return nextProps.id !== this.props.id; // 仅当 id 变化时重新渲染
}
2
3
使用场景-性能敏感组件
// 对复杂计算或大数据量的组件进行优化。
class HeavyComponent extends React.Component {
shouldComponentUpdate(nextProps) {
// 仅当数据长度变化时重新渲染
return nextProps.data.length !== this.props.data.length;
}
render() {
return <ExpensiveRender data={this.props.data} />;
}
}
2
3
4
5
6
7
8
9
10
与 React.PureComponent 的关系
PureComponent 是内置了 shouldComponentUpdate 的浅比较(shallow compare)的组件,自动对比 props 和 state,适合简单数据。
class User extends React.PureComponent { // 自动浅比较 props 和 state
render() {
return <div>{this.props.userId}</div>;
}
}
2
3
4
5
函数组件的替代方案
(1)useState + useEffect
function Input({ defaultValue }) {
const [value, setValue] = useState(defaultValue);
// 当 defaultValue 变化时,重置 value
useEffect(() => {
setValue(defaultValue);
}, [defaultValue]);
return <input value={value} onChange={e => setValue(e.target.value)} />;
}
2
3
4
5
6
7
8
9
10
(2)完全受控组件
function Input({ value, onChange }) {
return <input value={value} onChange={onChange} />;
}
2
3
常见问题
- shouldComponentUpdate 和 PureComponent 的区别?
- shouldComponentUpdate 需手动实现比较逻辑,PureComponent 自动浅比较。
- 为什么不能在 shouldComponentUpdate 中深度比较?
- 深度比较(如递归遍历对象)可能比重新渲染更消耗性能。
- 函数组件如何实现类似功能?
- 使用 React.memo 或 useMemo/useCallback 优化渲染。
- 什么情况下 shouldComponentUpdate 会失效?
- 父组件传递了新的引用类型(如内联函数或对象),导致浅比较无法检测到实际数据未变化。
# 3. render()
- 重新计算 UI。
# 4. getSnapshotBeforeUpdate(prevProps, prevState)
- 调用时机:在 DOM 更新前调用(render 之后,
componentDidUpdate
之前)。 - 用途:捕获 DOM 信息(如滚动位置),返回值会传给
componentDidUpdate
。 - ⚠️ 注意:必须配合
componentDidUpdate
使用。
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.items.length < this.props.items.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop; // 返回滚动位置
}
return null;
}
2
3
4
5
6
7
# 5. componentDidUpdate(prevProps, prevState, snapshot)
- 调用时机:DOM 更新后调用。
- 用途:
- 对比新旧 props/state 执行逻辑(如重新请求数据)
- 手动操作 DOM(如动画)
- ⚠️ 注意:
- 可以调用 setState,但必须加条件判断,否则会死循环。
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
fetch(`/api/user/${this.props.userId}`);
}
}
2
3
4
5
# 三. 卸载阶段(Unmounting)
# 1. componentWillUnmount()
- 调用时机:组件从 DOM 移除前调用。
- 用途:清理副作用,如:
- 取消 网络请求(AbortController)
- 移除 事件监听(window.removeEventListener)
- 清除 定时器(clearInterval)
componentWillUnmount() {
clearInterval(this.timer);
window.removeEventListener('resize', this.handleResize);
}
2
3
4
# 四. 错误处理(Error Boundaries)
# 1. static getDerivedStateFromError(error)
- 调用时机:子组件抛出错误时触发。
- 用途:更新 state 显示错误 UI(如
<ErrorFallback />
)。
static getDerivedStateFromError(error) {
return { hasError: true };
}
2
3
# 2. componentDidCatch(error, info)
- 调用时机:子组件抛出错误后调用。
- 用途:记录错误日志(如 Sentry)。
componentDidCatch(error, info) {
logErrorToService(error, info.componentStack);
}
2
3
# 五. 废弃的生命周期方法(React 16.3+)
废弃方法 | 替代方案 |
---|---|
componentWillMount | 逻辑移到 constructor 或 componentDidMount |
componentWillReceiveProps | static getDerivedStateFromProps |
componentWillUpdate | getSnapshotBeforeUpdate |
# 六. 常见问题
- componentDidMount 和 componentDidUpdate 的区别?
- componentDidMount 只在挂载后执行一次,componentDidUpdate 在每次更新后执行。
- 如何避免不必要的渲染?
- 使用 shouldComponentUpdate 或 React.PureComponent(自动浅比较 props/state)。
- getSnapshotBeforeUpdate 的使用场景?
- 在 DOM 更新前捕获信息(如滚动位置),用于 componentDidUpdate 恢复 UI 状态。
- 为什么废弃 componentWillMount?
- 它可能在 渲染前或渲染后执行(SSR 环境下不确定),推荐用 constructor 或componentDidMount 替代。
# 总结
- 挂载阶段:
constructor
→getDerivedStateFromProps
→render
→componentDidMount
- 更新阶段:
getDerivedStateFromProps
→shouldComponentUpdate
→render
→getSnapshotBeforeUpdate
→componentDidUpdate
- 卸载阶段:
componentWillUnmount
- 错误处理:
getDerivedStateFromError
+componentDidCatch
# 2.Hooks 生命周期
# 一. 生命周期对应关系(Class vs Hooks)
Class 生命周期 | Hooks 替代方案 |
---|---|
constructor | useState 初始化状态 |
componentDidMount | useEffect(() => {}, []) |
componentDidUpdate | useEffect(() => {}, [dep]) |
componentWillUnmount | componentWillUnmount |
shouldComponentUpdate | React.memo + useMemo/useCallback |
getDerivedStateFromProps | useState + useEffect 手动对比 props |
# 2. 核心 Hooks 实现生命周期
# (1)useState:替代 constructor 和状态管理
const [count, setCount] = useState(0); // 初始化状态
# (2)useEffect:模拟挂载、更新、卸载
componentDidMount(只执行一次)
useEffect(() => {
console.log("组件挂载完成");
}, []); // 空依赖数组
2
3
componentDidUpdate(依赖变化时触发)
useEffect(() => {
console.log("count 更新:", count);
}, [count]); // 监听 count 变化
2
3
componentWillUnmount(清理副作用)
useEffect(() => {
const timer = setInterval(() => {}, 1000);
return () => clearInterval(timer); // 返回清理函数
}, []);
2
3
4
# (3)useMemo/useCallback:替代 shouldComponentUpdate
避免不必要的渲染:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const memoizedCallback = useCallback(() => doSomething(a, b), [a, b]);
2
# (4)useRef:存储 DOM 引用或可变值
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus(); // 模拟 componentDidMount 的 DOM 操作
}, []);
2
3
4
# 三. 完整示例:Hooks 生命周期模拟
import { useState, useEffect, useRef } from "react";
function LifecycleDemo() {
const [count, setCount] = useState(0);
const mountedRef = useRef(false);
// 模拟 componentDidMount
useEffect(() => {
console.log("组件挂载");
mountedRef.current = true;
return () => {
console.log("组件卸载");
mountedRef.current = false;
};
}, []);
// 模拟 componentDidUpdate
useEffect(() => {
if (mountedRef.current) {
console.log("count 更新:", count);
}
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 四. 常见问题
- useEffect 和 componentDidMount 的区别?
- useEffect 是异步的(在渲染后执行),componentDidMount 是同步的。
- 如何精确控制更新逻辑?
- 通过 useEffect 的依赖数组(如 [count])或 useMemo/useCallback 优化性能。
- 为什么 Hooks 没有 getDerivedStateFromError?
- 错误边界仍需使用 Class 组件(如 static getDerivedStateFromError)。
# 总结
- 挂载阶段:useState + useEffect(() => {}, [])
- 更新阶段:useEffect(() => {}, [dep]) + useMemo/useCallback
- 卸载阶段:useEffect 的清理函数(return () => {})
# 3.Hooks的出现是为了解决什么问题
# 前言
比如,有这样一个函数组件(没有hooks,无状态),只是包含一个简单的div的Button组件:
function Button() {
return <div className="btn">disabled</div>;
}
2
3
由于需求变更,这个函数组件需要有内部的状态,为此需要重构成class组件
export default class Button extends React.Component {
state = { enabled: false };
render() {
const { enabled } = this.state;
const btnText = enabled ? "enabled" : "disabled";
return (
<div
className={`btn enabled-${enabled}`}
onClick={() => this.setState({ enabled: !enabled })}
>
{btnText}
</div>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 一、Class架构设计问题
# 1. 代码复用困难
在 Class 组件中,共享状态逻辑很麻烦,只能通过 高阶组件(HOC) 或 Render Props,代码变得复杂且难以维护。
// 创建一个高阶组件(HOC)来共享逻辑
function withMousePosition(WrappedComponent) {
return class extends React.Component {
state = { x: 0, y: 0 };
componentDidMount() {
window.addEventListener("mousemove", this.handleMouseMove);
}
componentWillUnmount() {
window.removeEventListener("mousemove", this.handleMouseMove);
}
handleMouseMove = (event) => {
this.setState({ x: event.clientX, y: event.clientY });
};
render() {
return <WrappedComponent {...this.props} mouse={this.state} />;
}
};
}
// 使用高阶组件
class MyComponent extends React.Component {
render() {
return <h1>Mouse Position: {this.props.mouse.x}, {this.props.mouse.y}</h1>;
}
}
export default withMousePosition(MyComponent);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 问题:
- 需要写一个 HOC,逻辑不直观
- 逻辑拆分困难,复用时组件层级变多
- 组件嵌套层级变深,导致 "Wrapper Hell"
# 2. Class 组件 this 指向困扰
Class 组件使用 this.setState
更新状态,导致 this
需要手动绑定,容易出错。
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.increment = this.increment.bind(this); // 必须绑定
}
increment() {
this.setState({ count: this.state.count + 1 });
}
render() {
return <button onClick={this.increment}>Click Me: {this.state.count}</button>;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
- 问题:
componentDidMount
里有多个逻辑(数据请求、监听事件)componentWillUnmount
需要手动清理副作用- 代码难以拆分和复用
# 3. 生命周期方法不清晰
Class 组件的生命周期函数 componentDidMount
、componentDidUpdate
、componentWillUnmount
职责混杂,一个生命周期函数可能涉及多个逻辑,导致代码难以管理。
// 生命周期混乱
class Example extends React.Component {
componentDidMount() {
this.fetchData();
window.addEventListener("resize", this.handleResize);
}
componentWillUnmount() {
window.removeEventListener("resize", this.handleResize);
}
fetchData() {
// 获取数据
}
handleResize = () => {
// 处理窗口大小变化
};
render() {
return <div>Example Component</div>;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 问题:
componentDidMount
里有多个逻辑(数据请求、监听事件)componentWillUnmount
需要手动清理副作用- 代码难以拆分和复用
# 二、Hooks 解决的问题
# 1. useCustom Hook 解决状态存储和复用
import { useState, useEffect } from "react";
// 自定义 Hook
function useMousePosition() {
const [mouse, setMouse] = useState({ x: 0, y: 0 });
useEffect(() => {
const updateMouse = (e) => setMouse({ x: e.clientX, y: e.clientY });
window.addEventListener("mousemove", updateMouse);
return () => {
window.removeEventListener("mousemove", updateMouse);
};
}, []);
return mouse;
}
// 组件中使用
function MyComponent() {
const mouse = useMousePosition();
return <h1>Mouse Position: {mouse.x}, {mouse.y}</h1>;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- ✔ 优势:
- 逻辑封装成
useMousePosition
,可复用 - 组件结构扁平,避免 "Wrapper Hell"
- 逻辑更清晰
- 逻辑封装成
# 2. useState 让函数组件有状态
import React, { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Click Me: {count}</button>;
}
2
3
4
5
- ✔ 优势:
- 去掉了 class,函数式更简单
- 不用手动绑定 this,不会有 this 相关 bug
# 3. useEffect 让函数组件有生命周期能力
import React, { useState, useEffect } from "react";
function Example() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize); // 清理副作用
};
}, []);
return <div>Window Width: {width}</div>;
}
2
3
4
5
6
7
8
9
10
11
12
13
- ✔ 优势:
- useEffect 统一管理副作用,不再需要
componentDidMount
和componentWillUnmount
- 避免了生命周期拆分困难的问题
- useEffect 统一管理副作用,不再需要
# 三、Hooks是如何解决以上问题?
# 1. Hooks 的状态存储和复用
Class 组件状态存储
在 Class 组件中, state 存在于组件实例上
class Counter extends React.Component {
constructor() {
super();
this.state = { count: 0 };
}
}
2
3
4
5
6
React 内部会创建 Counter 组件的 实例对象
const instance = new Counter();
- 状态存储在实例 ( instance.state ) 上
- this 让 state 成为组件的属性
- 复用逻辑必须靠继承、HOC 或者 Render Props
Hooks 组件状态存储
Hooks 不使用类实例存储状态,而是存储在 React Fiber 的链表中:
function Counter() {
const [count, setCount] = useState(0);
}
2
3
- 每个函数组件都有一个 hooks 数组,存储 useState 的状态
- 每次 useState 都会从 hooks 数组里取值
- 组件重新渲染时, useState 仍然能找到之前的值
# 2.为什么 Hooks 没有 this 困扰?
- Hooks 是基于闭包的,而不是基于类实例
- 每次渲染时,Hooks 创建一个全新的作用域
- useState 返回的是 局部变量,而不是实例属性,不需要 this
在 React 内部,Class 组件的 state 是绑定在实例上的:
class Counter extends React.Component {
constructor() {
super();
this.state = { count: 0 };
}
}
2
3
4
5
6
而 Hooks 直接使用一个数组或链表来存储状态,避免 this 的问题:
function Counter() {
const [count, setCount] = useState(0); // 这里 count 只是一个变量,不属于实例
}
2
3
在 函数组件重新执行时,它创建了一个新的作用域,count 变量 不会丢失,因为 React 通过一个 隐藏的数组存储每次渲染的状态。
# 3. 工作原理
Hooks 的运行机制 依赖 React Fiber 架构,它通过 链表结构管理每次组件的状态。
Fiber 是什么?Fiber 是 React 内部维护的 组件更新数据结构,它类似于 链表结构:
const fiber = {
type: Counter, // 组件类型
stateNode: null, // 组件实例
child: null, // 子 Fiber
sibling: null, // 兄弟 Fiber
return: null, // 父 Fiber
hooks: [], // 存储 useState/useEffect 的状态
};
2
3
4
5
6
7
8
Fiber 中的 hooks 数组存储组件的状态!
# 4. Hooks 必须在最顶层调用?
- React 按顺序存储 Hooks
- 如果条件变化,Hook 调用顺序会错乱
- React 通过 hookIndex 读取状态,顺序变了就会报错
# 4.基础Hooks
React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。
React 默认提供的三个最常用的钩子。
- useState() 状态钩子
- useEffect() 用于在函数组件中执行副作用
- useLayoutEffect() 和useEffect类似,但是执行时机不同
- useContext() 跨组件传值
# 2.1 useState详解
import React, { useState } from "react";
export default function Button() {
// 第一个成员是一个变量
// 第二个成员是一个函数,用来更新状态,约定是set前缀加上状态的变量名
const [buttonText, setButtonText] = useState("Click me, please"); // 类似class组件的state和setState
function handleClick() {
return setButtonText("Thanks, been clicked!");
}
return <button onClick={handleClick}>{buttonText}</button>;
}
2
3
4
5
6
7
8
9
10
11
12
13
# useState 进阶用法
1. 如果新状态依赖旧状态,推荐使用函数式更新:
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount((prevCount) => prevCount + 1); // 基于旧值计算
};
return <button onClick={increment}>Count: {count}</button>;
}
2
3
4
5
6
7
- 适用场景:
- 避免闭包问题(如 setTimeout 内更新状态)。
- 确保多次 setState 按预期执行(避免合并更新)。
2. 惰性初始化
function ExpensiveInitialState() {
const [state, setState] = useState(() => {
const initialState = calculateHeavyValue(); // 只计算一次
return initialState;
});
}
2
3
4
5
6
3. 合并更新(对象/数组)
React 不会自动合并对象/数组的更新,需手动处理:
function UserProfile() {
const [user, setUser] = useState({ name: "Alice", age: 25 });
const updateName = () => {
setUser(prev => ({ ...prev, name: "Bob" })); // 保留其他属性
};
}
2
3
4
5
6
7
4. setState 是异步的,不能立即获取新值
function AsyncExample() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
console.log(count); // ❌ 仍然是旧值
};
}
2
3
4
5
6
7
8
✅ 解决方案
useEffect(() => {
console.log(count); // ✅ 在 useEffect 中获取新值
}, [count]);
2
3
# 2.2 useEffect详解
用于在函数组件中执行副作用,例如数据获取、订阅、手动 DOM 操作等。它相当于class组件中的 componentDidMount
、componentDidUpdate
和 componentWillUnmount
的组合。
# (1) useEffect 基本语法
useEffect(() => {
// 副作用逻辑(组件渲染后执行)
return () => {
// 清理逻辑(组件卸载或依赖项变化前执行)
};
}, [dependencies]); // 依赖项数组
2
3
4
5
6
- 第一个参数(回调函数):执行副作用的逻辑。
- 第二个参数(依赖项数组):控制 useEffect 何时重新执行。
- 返回值(清理函数):在组件卸载或依赖项变化前执行清理逻辑。
# (2) useEffect 的 4 种使用方式
1. 无依赖项([])—— 只在首次渲染后执行
useEffect(() => {
console.log("组件挂载后执行(类似 componentDidMount)");
}, []); // 空数组表示不依赖任何变量
2
3
- 适用场景:
- 数据初始化(如
fetch
请求) - 事件监听(如
window.addEventListener
) - 定时器(如
setInterval
)
- 数据初始化(如
2. 有依赖项([dep1, dep2])—— 依赖项变化时执行
const [count, setCount] = useState(0);
useEffect(() => {
console.log("count 变化时执行(类似 componentDidUpdate)");
}, [count]); // 仅在 count 变化时重新执行
2
3
4
5
- 适用场景:
- 数据变化时重新计算(如
useMemo
的替代) - 监听
props
或state
变化(如props.id
变化时重新请求数据)
- 数据变化时重新计算(如
3. 无依赖项(无第二个参数)—— 每次渲染后都执行
useEffect(() => {
console.log("每次渲染后都执行");
}); // 没有依赖项数组
2
3
- ⚠️ 注意:
- 可能导致性能问题(频繁执行)
- 适用于需要实时同步的场景(如
document.title
更新)
4. 清理副作用(return 清理函数)
useEffect(() => {
const timer = setInterval(() => {
console.log("每秒执行");
}, 1000);
return () => {
console.log("(类似 componentWillUnmount)");
clearInterval(timer); // 组件卸载时清理定时器
};
}, []);
2
3
4
5
6
7
8
9
10
- 适用场景:
- 取消订阅(如
WebSocket
、EventEmitter
) - 清理定时器(如
setTimeout
、setInterval
) - 手动 DOM 操作(如
document.removeEventListener
)
- 取消订阅(如
# 2.3 useEffect详解
useLayoutEffect
是 React 提供的一个 Hook,与 useEffect 类似,但执行时机不同。它适用于需要在 浏览器绘制前同步执行的场景(如测量 DOM 布局、避免 UI 闪烁等)。
执行时机:在 React 完成 DOM 更新后,浏览器绘制(paint)前同步执行。
对比项 | useEffect | useLayoutEffect |
---|---|---|
执行时机 | 异步(浏览器绘制后) | 同步(DOM 更新后,浏览器绘制前) |
适用场景 | 数据获取、订阅、非关键副作用 | 测量 DOM、强制同步渲染(避免闪烁) |
性能影响 | 对渲染性能影响较小 | 可能阻塞渲染,慎用 |
# useLayoutEffect 核心用途
1. 测量 DOM 元素尺寸/位置
import { useLayoutEffect, useRef, useState } from "react";
function Tooltip() {
const ref = useRef(null);
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
// 在浏览器绘制前获取 DOM 尺寸
const { width } = ref.current.getBoundingClientRect();
setWidth(width);
}, []);
return <div ref={ref}>Tooltip width: {width}px</div>;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 为什么不用 useEffect?
- 如果使用
useEffect
,用户可能会先看到未计算的布局,再闪烁调整,而useLayoutEffect
能避免这种闪烁。
- 如果使用
2. 手动 DOM 操作(如动画库集成)
useLayoutEffect(() => {
const element = document.getElementById("my-element");
// 在浏览器绘制前应用动画
element.style.transform = "translateX(100px)";
}, []);
2
3
4
5
# useLayoutEffect 注意事项
- (1) 避免阻塞主线程
useLayoutEffect
是同步执行的,如果逻辑复杂,会导致页面卡顿。- 优化方案:将耗时计算放到
useEffect
或requestAnimationFrame
中。
- (2) 服务端渲染(SSR)问题
- 在 SSR 环境下,
useLayoutEffect
会触发警告(因为无法在服务端执行 DOM 操作)。
- 在 SSR 环境下,
# 2.4. useContext
官方定义:在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但此种用法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
Context提供了一个局部的全局作用域,使用Context则无需再手动的逐层传递props。
3种Context的使用方式:
React.createContext
提供的Provider
和
Consumer- 函数组件:
React.createContext
提供的Provider
和useContext
钩子 - Class组件:
React.createContext
提供的Provider
和class的contextType
属性
先定义好createContext
// Context.jsx
import { createContext } from "react";
export default createContext();
2
3
4
# 2.4.1. React.createContext提供的Provider和Consumer
// App.js
import React, { createContext } from "react";
import MyContext from "./Context";
import FirstTest from "./FirstTest";
export default function App() {
return (
//Provider组件接收一个value属性,此处传入一个带有name属性的对象
<MyContext.Provider value={{ name: `test` }}>
<FirstTest />
</MyContext.Provider>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
// FirstTest.jsx
import React from "react";
import MyContext from "./Context";
const FirstTest = () => {
return (
<MyContext.Consumer>
{(value) => {
return (
<div>{JSON.stringify(value)}</div> // test
);
}}
</MyContext.Consumer>
);
};
export default FirstTest;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2.4.2. 函数组件:React.createContext提供的Provider和useContext钩子
// SecondTest.jsx
import React, { useContext } from "react";
import MyContext from "./Context";
const SecondTest = () => {
const context = useContext(MyContext);
return <div>{JSON.stringify(value)}</div>; // test
};
export default SecondTest;
2
3
4
5
6
7
8
9
10
# 2.4.3. Class组件:React.createContext提供的Provider和class的contextType属性
// ThirdTest.jsx
import React, { Component } from "react";
import context from "./Context";
class ThirdTest extends Component {
static contextType = context;
render() {
const value = this.context;
return <div>{JSON.stringify(value)}</div>; // test
}
}
// ThirdTest.contextType = context; //此处与写static关键字作用一致
export default ThirdTest;
2
3
4
5
6
7
8
9
10
11
12
13
14
# 5.进阶Hooks
- useReducer() 复杂状态管理,useState 的替代方案
- useCallback() 记忆函数
- useMemo() 缓存记忆值,类似Vue的computed
- useRef() 持久化引用
- useImperativeHandle() 自定义暴露给父组件的实例值
# 3.1 useReducer详解(看起来写的更复杂了,还需要进一步了解)
useReducer
是 useState
的替代方案,适用于状态逻辑较复杂的组件。
const [state, dispatch] = useReducer(reducer, initialState);
示例:待办事项列表
function todosReducer(state, action) {
switch (action.type) {
case 'ADD':
return [...state, { text: action.text, completed: false }];
case 'TOGGLE':
return state.map((todo, index) =>
index === action.index ? {...todo, completed: !todo.completed} : todo
);
default:
return state;
}
}
function TodoList() {
const [todos, dispatch] = useReducer(todosReducer, []);
return (
<>
<button onClick={() => dispatch({ type: 'ADD', text: 'New Task' })}>
Add Todo
</button>
{todos.map((todo, index) => (
<div key={index}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => dispatch({ type: 'TOGGLE', index })}
/>
{todo.text}
</div>
))}
</>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
- 适用场景
- 状态逻辑复杂,有多个子值
- 下一个状态依赖于之前的状态
- 需要集中管理状态更新逻辑
# 3.2 useCallback详解
useCallback
返回一个记忆化的回调函数,只有当依赖项改变时才会更新。
# 示例:优化子组件渲染
在不使用 useCallback()
的时候,每次触发 handleAdd
事件时候,都会渲染子组件 ChildA
// 父组件
import { useCallback, useState} from 'react'
import ChildA from './childA'
export default function MyCallBack() {
const [useInfo, setUseInfo] = useState({
name: 'Andy',
age: 18
})
const myCallback = () => {
console.log('==useCallback==')
return useInfo.name
}
const handleAdd = () => {
setUseInfo({
name: 'Andy',
age: 18
})
}
return (
<div>
<button onClick={handleAdd}>add</button>
<ChildA onAddCount={myCallback} ></ChildA>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 子组件
import React, { memo } from 'react'
const ChildA = memo(({onAddCount}) => {
console.log('==ChildA 组件更新了=', count)
return (
<button onClick={onAddCount}>子组件</button>
)
})
2
3
4
5
6
7
8
9
当我们点击 add 按钮时候,发现页面打印 “==ChildA 组件更新了=”,说明传入相同的数据时候,会触发子组件渲染
# 使用 useCallback 时候
const myCallback = useCallback(() => {
console.log('==useCallback==')
return useInfo.name
}, [useInfo.name])
2
3
4
在点击 add 按钮 更新相同数据时候,只有父组件渲染,子组件不会再渲染
- 若要实现 传入相同数据时候,只更新当前组件,而子组件不进行渲染,需使用
useCallback()
和memo
来处理;
# 3.3 useMemo详解
useMemo
是 React 提供的一个 hook 函数。允许开发人员缓存变量的值和依赖列表。如果此依赖项列表中的任何变量发生更改,React 将重新运行此函数去处理并重新缓存它。如果依赖项列表中的变量值没有改版,则 React 将从缓存中获取值。
基本可以等价于Vue 的 computed
React 官方文档介绍
TIP
You may rely on useMemo as a performance optimization 您可以依赖 useMemo 作为性能优化工具
- 基本用法
import React, { useMemo, useState } from "react";
export default function App() {
const [myNum, setMyNum] = useState(0);
const [otherNum, setOtherNum] = useState(0);
const newVal = useMemo(() => {
return myNum + otherNum;
}, [myNum, otherNum]);
const addOneNum = () => {
let val = myNum + 1;
setMyNum(val);
};
const addTwoNum = () => {
let val = otherNum + 2;
setOtherNum(val);
};
return (
<div className="App">
result:{newVal}
<button onClick={addOneNum}>addOneNum</button>
<button onClick={addTwoNum}>addTwoNum</button>
</div>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
useMemo第一个参数是函数,第二个参数是数组,数组里面的元素是依赖项
# 3.4 useRef - 持久化引用
useRef 返回一个可变的ref对象,其.current属性被初始化为传入的参数
# 示例1:访问DOM元素
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 示例2:保存可变值
function Timer() {
const [count, setCount] = useState(0);
const timerRef = useRef();
useEffect(() => {
timerRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(timerRef.current);
}, []);
return <div>Count: {count}</div>;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
- 适用场景
- 访问DOM节点
- 存储可变值而不触发重新渲染
- 保存前一次渲染的值
# 3.5 useImperativeHandle - 自定义暴露给父组件的实例值
useImperativeHandle 可以让你在使用ref时自定义暴露给父组件的实例值。
const FancyInput = React.forwardRef(function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
getValue: () => {
return inputRef.current.value;
}
}));
return <input ref={inputRef} />;
});
function Parent() {
const inputRef = useRef();
return (
<>
<FancyInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
<button onClick={() => console.log(inputRef.current.getValue())}>
Log Value
</button>
</>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
- 适用场景
- 暴露子组件的特定方法给父组件
- 限制父组件对子组件的访问
# 3.6 useDebugValue - 自定义Hook调试标签
useDebugValue
用于在React开发者工具中显示自定义Hook的标签。
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// 在开发者工具中显示标签
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
2
3
4
5
6
7
8
- 适用场景
- 自定义Hook开发
- 在React开发者工具中提供更有意义的调试信息
# 6.React diff算法
React 的 Diff 算法基于以下 三大策略 优化性能:
同级比较(Tree Diff)
、组件类型一致性
、key 属性优化
1.同级比较(Tree Diff)
- 仅对比 同一层级 的节点,不跨层级比较(复杂度从 O(n³) 降至 O(n))。
- 如果节点类型不同(如
div
→span
),直接销毁并重建整个子树。
2.组件类型一致性
- 相同类型的组件(如
<Button />
)会复用实例,触发更新逻辑(如shouldComponentUpdate
)。 - 不同类型则销毁旧组件,挂载新组件。
- 相同类型的组件(如
3.key 属性优化
- 对列表节点,
key
帮助 React 识别节点的唯一性,减少不必要的销毁/重建。
- 对列表节点,
React的diff 算法采用了 深度优先遍历算法
- react 采用单指针从左向右进行遍历
- vue采用双指针,从两头向中间进行遍历
React 不能通过双端对比进行 Diff 算法优化是因为目前 Fiber 上没有设置反向链表。
# 7.组件通信方式
# 一. 父子组件通信
# (1)父 → 子:props 传递数据
// 父组件
function Parent() {
const [count, setCount] = useState(0);
return <Child count={count} onUpdate={() => setCount(count + 1)} />;
}
// 子组件
function Child({ count, onUpdate }) {
return <button onClick={onUpdate}>Count: {count}</button>;
}
2
3
4
5
6
7
8
9
# (2)子 → 父:回调函数
// 父组件
function Parent() {
const [message, setMessage] = useState("");
const handleChildMessage = (msg) => setMessage(msg);
return <Child onSend={handleChildMessage} />;
}
// 子组件
function Child({ onSend }) {
return <button onClick={() => onSend("Hello!")}>发送消息</button>;
}
2
3
4
5
6
7
8
9
10
# (3)父 → 子:ref 调用子组件方法
父组件通过 useRef 获取子组件实例并调用其方法(需配合 forwardRef
+ useImperativeHandle
)
// 子组件(暴露方法)
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
showAlert: () => alert("子组件方法被调用"),
}));
return <div>Child</div>;
});
// 父组件
function Parent() {
const childRef = useRef();
return (
<>
<Child ref={childRef} />
<button onClick={() => childRef.current.showAlert()}>触发子组件方法</button>
</>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 二. 兄弟组件通信
# (1)状态提升(Lifting State Up)
将共享状态提升到最近的共同父组件,通过 props 传递。
function Parent() {
const [sharedData, setSharedData] = useState("");
return (
<>
<SiblingA data={sharedData} />
<SiblingB onUpdate={setSharedData} />
</>
);
}
function SiblingA({ data }) {
return <div>接收数据: {data}</div>;
}
function SiblingB({ onUpdate }) {
return <button onClick={() => onUpdate("New Data")}>更新数据</button>;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# (2)通过事件总线(Event Bus)
使用自定义事件(如 EventEmitter)实现兄弟组件通信(非 React 推荐方式,慎用)。
// eventBus.js
const events = new EventEmitter();
export default events;
// 组件A(发布事件)
events.emit("update", "Data from A");
// 组件B(订阅事件)
useEffect(() => {
events.on("update", (data) => console.log(data));
return () => events.off("update"); // 清理
}, []);
2
3
4
5
6
7
8
9
10
11
12
# 三. 跨层级组件通信
# (1)Context API
通过 createContext + useContext 实现跨组件数据共享。
// 创建 Context
const ThemeContext = createContext("light");
// 父组件(提供数据)
function App() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
// 子组件(消费数据)
function Toolbar() {
const theme = useContext(ThemeContext);
return <div>当前主题: {theme}</div>;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# (2)状态管理库(Redux/Zustand/MobX)
适用于全局状态管理,如用户登录信息、主题等。
// Redux 示例
const store = configureStore({ reducer: counterReducer });
// 组件中获取状态
const count = useSelector((state) => state.counter);
const dispatch = useDispatch();
dispatch(increment());
2
3
4
5
6
# 四. 任意组件通信(高级场景)
# (1)发布-订阅模式(PubSub)
// 组件A(发布)
PubSub.publish("event", "Data");
// 组件B(订阅)
useEffect(() => {
const token = PubSub.subscribe("event", (_, data) => console.log(data));
return () => PubSub.unsubscribe(token);
}, []);
2
3
4
5
6
7
# (2)使用 useReducer + Context
复杂状态逻辑下,结合 useReducer 和 Context 管理状态。
const StateContext = createContext();
const DispatchContext = createContext();
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
<Child />
</DispatchContext.Provider>
</StateContext.Provider>
);
}
// 子组件中使用
const state = useContext(StateContext);
const dispatch = useContext(DispatchContext);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 五. 如何选择通信方式?
场景 | 推荐方式 |
---|---|
父子组件简单通信 | props + 回调函数 |
兄弟组件共享状态 | 状态提升或 Context |
跨多层组件共享数据 | Context API 或 Redux |
全局复杂状态管理 | Redux/Zustand/MobX |
临时事件通信 | 发布-订阅模式(慎用) |
# 6. 常见问题
- props 和 context 的区别?
- props 需手动逐层传递,context 可跨层级直接访问。
- Redux 和 Context 如何选择?
- Redux 适合大型应用,Context 适合中小型应用或局部状态。
- 为什么慎用事件总线?
- 容易导致代码难以维护,组件间隐式耦合。
# 总结
- 父子通信:props + 回调函数 + ref
- 兄弟通信:状态提升或 Context
- 跨层级通信:Context API 或状态管理库
- 全局状态:Redux/Zustand
- 临时通信:发布-订阅(谨慎使用)
# 8.Mobx 相关
# 1. MobX 核心概念
- 1.1 基本组成要素
- Observable (可观察状态):被 MobX 跟踪的状态
- Action (动作):修改状态的方法
- Computed (计算值):从状态派生的值
- Reaction (反应):对状态变化的响应
- 1.2 工作原理
- Action → State (Observable) → Computed Values → Reactions (如React组件渲染)
# 2. MobX 基本使用
# 2.1 创建 Store
import { makeAutoObservable } from "mobx";
class CounterStore {
count = 0; // 可观察状态
constructor() {
makeAutoObservable(this); // 自动转换
}
// Action
increment = () => { this.count++ };
// Action
decrement = () => { this.count-- };
// Computed
get doubleCount() { return this.count * 2 }
}
export default new CounterStore();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2.2 在 React 中使用
import { observer } from "mobx-react-lite";
import counterStore from "./CounterStore";
const Counter = observer(() => {
return (
<div>
<p>Count: {counterStore.count}</p>
<p>Double: {counterStore.doubleCount}</p>
<button onClick={counterStore.increment}>+</button>
<button onClick={counterStore.decrement}>-</button>
</div>
);
});
2
3
4
5
6
7
8
9
10
11
12
13
# 3. MobX 高级特性
# 3.1 手动定义 Observable
import { observable, action, computed } from "mobx";
class TodoStore {
@observable todos = [];
@observable filter = "all";
@action
addTodo = (text) => {
this.todos.push({ text, completed: false });
};
@computed
get filteredTodos() {
switch (this.filter) {
case "completed":
return this.todos.filter(todo => todo.completed);
case "active":
return this.todos.filter(todo => !todo.completed);
default:
return this.todos;
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 3.2 异步 Actions
import { runInAction } from "mobx";
class UserStore {
@observable users = [];
@observable state = "pending"; // "pending", "done", "error"
@action
async fetchUsers() {
this.state = "pending";
try {
const response = await fetch("/api/users");
const users = await response.json();
runInAction(() => {
this.users = users;
this.state = "done";
});
} catch (error) {
runInAction(() => {
this.state = "error";
});
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 4. MobX 与 React 集成
# 4.1 函数组件
import { observer } from "mobx-react-lite";
import { useStores } from "./stores";
const TodoList = observer(() => {
const { todoStore } = useStores();
return (
<div>
{todoStore.filteredTodos.map(todo => (
<TodoItem key={todo.id} todo={todo} />
))}
</div>
);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
# 9.Redux 相关
# 1. Redux 三大原则
- 单一数据源:整个应用状态存储在单个 store 中
- 状态只读:只能通过 dispatch action 来修改状态
- 纯函数修改:使用纯函数 reducer 处理状态变更
# 2. Redux 核心组成
- Action:描述发生了什么的对象,必须包含
type
属性 - Reducer:纯函数,接收旧 state 和 action,返回新 state
- Store:保存应用状态的对象,提供核心方法:
getState()
获取当前状态dispatch(action)
触发状态更新subscribe(listener)
注册监听器
# 3. Redux 工作流程
- 用户触发 UI 事件
- 调用
dispatch(action)
- Redux store 调用 reducer 函数
- Reducer 返回新 state
- Store 更新 state 并通知订阅者
- UI 根据新 state 重新渲染
- 基础 Redux 实现
// Action
const increment = () => ({ type: 'INCREMENT' });
// Reducer
const counter = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT': return state + 1;
default: return state;
}
}
// Store
const store = createStore(counter);
// 订阅 state 变化
const unsubscribe = store.subscribe(() => {
console.log('State changed:', store.getState());
});
// 使用
store.dispatch(increment());
console.log(store.getState()); // 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 4. Redux 中间件原理?常用中间件有哪些?
- 原理:提供
dispatch
和getState
的扩展点 - 常用:
redux-thunk
(异步)、redux-saga
(复杂副作用)、redux-logger
(日志)
# 5. Redux 如何处理异步操作?
- 使用中间件如 redux-thunk 或 redux-saga
- thunk 示例:
const fetchData = () => async dispatch => {
dispatch({type: 'FETCH_START'});
try {
const res = await api.getData();
dispatch({type: 'FETCH_SUCCESS', payload: res});
} catch (err) {
dispatch({type: 'FETCH_ERROR', error: err});
}
}
2
3
4
5
6
7
8
9
# 6. Redux 与 Context API 如何选择?
Redux
:大型应用、复杂状态管理、需要时间旅行调试Context
:中小型应用、简单状态共享
# MobX 与 Redux 对比
特性 | MobX | Redux |
---|---|---|
编程范式 | 响应式编程 | 函数式编程 |
性能优化 | 自动 | 手动 |
调试工具 | 有限 | 强大(Redux DevTools) |
适用场景 | 中小型应用,快速开发 | 大型应用,需要严格架构 |
# 10.Redux Toolkit 相关
# Redux Toolkit 解决了什么问题
- 简化
Redux
样板代码 - 内置
immer
实现不可变更新 - 提供
createSlice
自动生成action
和reduce
import { configureStore, createSlice } from '@reduxjs/toolkit';
const userSlice = createSlice({
name: 'user',
initialState: { name: '', age: 0 },
reducers: {
setName: (state, action) => {
state.name = action.payload;
},
setAge: (state, action) => {
state.age = action.payload;
}
}
});
const store = configureStore({
reducer: {
user: userSlice.reducer
}
});
// 使用
store.dispatch(userSlice.actions.setName('Alice'));
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 11.React fiber相关
React Fiber 是 React 16 中引入的全新协调算法,其核心目的是解决 React 15 及之前版本在渲染大型应用时的性能瓶颈和用户体验问题
# (1) 不可中断的递归更新
- 问题:组件的更新过程是一个深度优先递归调用,一旦开始渲染,必须一次性完成整个组件树的更新(类似函数调用栈)。
- 后果:
- 如果组件树很大(例如复杂页面),JavaScript 会长时间占用主线程(Main Thread),导致:
- 掉帧(Jank):浏览器无法及时处理用户交互(如点击、滚动)。
- 卡顿:页面响应延迟,尤其在低端设备上。
- 如果组件树很大(例如复杂页面),JavaScript 会长时间占用主线程(Main Thread),导致:
# (2) 缺乏优先级调度
- 问题:所有更新任务具有相同的优先级,无法区分高优先级更新(如用户输入)和低优先级更新(如数据加载)。
- 后果:
- 用户点击按钮时,可能被一个耗时的渲染任务阻塞,导致交互延迟。
# (3) 同步渲染(Blocking Rendering)
- 问题:渲染过程是同步的,无法拆分为小块任务。
- 后果:
- 无法利用浏览器的空闲时间(Idle Time)分片渲染。
# React Fiber 的解决方案
Fiber 通过以下机制彻底重构了 React 的渲染流程:
# (1) 可中断的异步渲染
- Fiber 将渲染任务拆解为多个小单元,每个单元对应一个组件或 DOM 节点。
- 关键改进:
- 使用 链表结构(而非递归)遍历组件树,允许渲染过程暂停、恢复或丢弃。
- 通过浏览器的
requestIdleCallback
(现改为 Scheduler)在空闲时间执行任务,避免阻塞主线程。
# (2) 优先级调度
Fiber 为不同任务分配优先级:
优先级类型 | 场景示例 | 调度策略 |
---|---|---|
Immediate | 用户输入、动画 | 同步执行(最高优先级) |
High | 交互反馈(如按钮状态) | 尽快执行 |
Low | 数据加载、离屏渲染 | 空闲时执行 |
结果:用户交互(如输入)始终优先响应,避免卡顿。
# (3) 增量渲染(Incremental Rendering)
- Fiber 将渲染分为两个阶段:
- Reconciliation Phase(协调阶段): 计算哪些组件需要更新(可中断,不直接操作 DOM)。
- Commit Phase(提交阶段): 一次性提交所有变更到 DOM(同步执行,不可中断)。
- 优势:协调阶段可以分片执行,避免长时间占用主线程。
# (4) 错误边界(Error Boundaries)
Fiber 支持在组件树中捕获异常,避免因局部错误导致整个应用崩溃(React 15 中未处理错误会白屏)
# Fiber 的实际优化场景
- (1) 大型列表渲染
- 旧版问题:渲染 1000 条列表项会卡死页面。
- Fiber 优化:分片渲染,优先显示可视区域内容(结合 React.lazy 或虚拟列表)。
- (2) 动画和交互响应
- 旧版问题:动画帧率不稳定。
- Fiber 优化:高优先级更新(如动画)优先执行,低优先级任务(如日志上报)延后。
- (3) Suspense 和并发模式(Concurrent Mode)
- 功能:允许组件等待异步数据时显示占位符(如 loading)。
- 底层依赖:Fiber 的异步渲染能力。
# 总结:Fiber 的核心价值
问题 | React 15(Stack) | React 16+(Fiber) |
---|---|---|
主线程阻塞 | 严重(同步递归) | 轻微(可中断的异步分片) |
优先级调度 | 不支持 | 支持(Immediate/High/Low) |
用户体验 | 卡顿、掉帧 | 流畅、响应迅速 |
扩展性 | 难以优化 | 支持 Suspense、并发渲染等新特性 |
← React基础 React18新特性 →