← 返回目录

第 4 卷

第 4 / 28 卷日期:

使用 Hooks 时的依赖变化:函数组件中, useEffect、 useMemo、 useCallback 等 Hooks 的依赖数组发生变化时,会触发对应的逻辑执行,若逻辑中涉及状态更新,会进一步触发组件渲染。

2. 组件重新渲染时,render 函数内部发生的事情

render 函数(类组件)或函数组件的返回值部分,是组件渲染 UI 的核心逻辑,重新渲染时会执行以下操作:

• 执行 render 函数/函数组件主体:

• 类组件:执行 render 方法,执行过程中会解析组件内部的 JSX 语法,同时计算组件内的变量、

表达式(如 {count + 1})。

• 函数组件:直接执行函数组件的主体代码,从函数开始到 return 语句结束,包括内部变量声明、

Hooks 调用(如 useState 会获取最新状态)、逻辑计算等。

生成虚拟 DOM(VNode)树:render 函数/函数组件的返回值(JSX)会被 Babel 编译为 React.createElement 调用,执行该调用后生成虚拟 DOM 树。虚拟 DOM 是对真实 DOM 的轻

量级描述,包含组件类型、属性(Props)、子节点等信息。

执行 Diff 算法对比新旧虚拟 DOM:React 会将本次生成的新虚拟 DOM 树与上一次渲染保存的旧虚拟 DOM 树进行对比(Diff 算法),找出两者的差异(如节点增删、属性变化、文本内容变化等)。

生成更新补丁并应用到真实 DOM:根据 Diff 算法的对比结果,React 会生成最小化的 DOM 更新补丁(只更新变化的部分),然后通过渲染层(如 react-dom)将这些补丁应用到真实 DOM 中,完成页

面的更新。

注意事项:

• render 函数是「纯函数」(理想情况下),不应包含副作用操作(如修改状态、调用接口、操作

DOM),否则会导致渲染逻辑混乱或性能问题。

• 重新渲染时,若 Diff 算法发现新旧虚拟 DOM 无差异,React 会跳过真实 DOM 的更新操作,这是

React 的核心性能优化点之一。

八、避免 React 中不必要渲染的性能优化方案

不必要的渲染(如父组件渲染导致子组件无意义跟随渲染、状态未变却触发渲染)会浪费性能,尤其是复杂应用中,需通过以下方案优化:

1. 类组件优化方案

• 使用 React.PureComponent:纯组件自动实现了 shouldComponentUpdate 方法,对

• 手动重写 shouldComponentUpdate:对于复杂场景(如嵌套对象的 props),可手动实现

• 避免在 render 中创建新对象/新函数:render 中创建的新对象(如 { name: 'xxx' })、新

函数(如 onClick={() => this.handleClick()})会导致每次渲染时 props 引用变化,即使内容相同,浅比较也会认为 props 变化,触发子组件渲染。解决方案:将对象/函数提升

到组件实例属性(如 this.obj = { name: 'xxx' })或使用箭头函数绑定在构造函数中。

2. 函数组件优化方案

• 使用 React.memo 包装组件: React.memo 是高阶组件,用于函数组件的性能优化,类似类组

件的 PureComponent,会对组件的 props 进行浅比较,只有 props 变化时才触发渲染。用法: const MemoComponent = React.memo(FunctionComponent);若需自定义对比

逻辑,可传入第二个参数(对比函数)。

• 使用 useMemo 缓存计算结果:对于组件内的复杂计算逻辑(如大数据过滤、排序),每次渲染都

会重新执行,浪费性能。 useMemo 可缓存计算结果,仅当依赖数组中的值变化时才重新计算。用法: const result = useMemo(() => computeFn(data), [data])。

• 使用 useCallback 缓存函数引用:函数组件中,每次渲染都会重新创建函数,导致传递给子组件的

函数 props 引用变化,触发子组件不必要渲染。 useCallback 可缓存函数引用,仅当依赖数组变化时才创建新函数。用法: const handleClick = useCallback(() => { ... },

[deps])。

3. 通用优化方案

• 拆分组件为更小的粒度:将复杂组件拆分为多个独立的子组件,使得状态变化时,只有受影响的子

组件重新渲染,而非整个复杂组件。例如:将列表项拆分为独立的 ListItem 组件,单个列表项状态变化时,仅该组件渲染。

• 合理使用 Context 或状态管理库:避免将过多无关状态放入 Context 中,否则 Context 状态变化

时,所有订阅该 Context 的组件都会渲染。可将 Context 拆分为多个细分 Context,或使用 Redux、Zustand 等状态管理库,实现状态的精准订阅(仅依赖该状态的组件才渲染)。

• 避免不必要的状态更新:调用 setState 或 useState 更新函数时,确保传入的新状态与旧

状态确实不同。例如:类组件中可先判断再更新( if (newState !== this.state.count) this.setState({ count: newState }));函数组件中,

useState 会自动忽略与旧状态相同的新状态(基本类型)。

• 使用懒加载和代码分割:通过 React.lazy 和 Suspense 实现组件的懒加载,避免初始渲染

时加载过多无关组件,减少初始渲染时间。用法: const LazyComponent = React.lazy(() => import('./LazyComponent')),配合 Suspense 展示加载占位

九、setState 的调用原理及 React 内部执行流程

1. setState 的调用原理

setState 是 React 类组件中更新状态的核心方法,其核心原理是「触发组件的重新渲染」,但并非直接修改 this.state(直接修改不会触发渲染),而是通过 React 内部的状态更新机制,异步且批

量地处理状态更新,最终完成组件渲染。

关键特性:setState 是「异步更新」(多数场景下)、「批量更新」、「可能被合并」,其原理依赖于 React 的调度系统和协调系统。

2. 调用 setState 后,React 内部发生的事情(详细流程)

  1. 接收状态更新请求,加入更新队列:
  2. 调用 setState(partialState, callback) 时,React 首先会接收传入的

partialState(可以是对象或函数),并将其加入到当前组件的「更新队列」中(更新队列是存储组件所有待处理状态更新的队列)。

  1. 若 partialState 是函数,React 会在后续处理时调用该函数,传入当前的 state 和

props,获取最终的新状态(便于依赖前一个状态计算新状态,如 this.setState(prevState => ({ count: prevState.count + 1 })))。

  1. 判断是否需要批量更新:
  2. React 会检查当前执行环境是否处于「批量更新上下文」中(如合成事件回调、生命周期方法、

React 钩子函数内部)。若是,则将当前更新加入批量更新队列,等待所有批量更新收集完成后统一处理;若否(如原生事件、setTimeout、Promise 回调中),则直接触发后续的更新流程

(React18 后通过自动批处理,大部分场景都支持批量更新)。

  1. 触发调度器(Scheduler)调度更新:
  2. 批量更新收集完成后,React 会调用调度器(Scheduler)的 scheduleCallback 方法,将状

态更新任务加入调度队列,并根据任务优先级排序(如用户输入是高优先级,普通状态更新是低优先级)。

  1. 调度器负责时间切片(Time Slicing),确保高优先级任务先执行,避免长时间占用主线程导致页

面卡顿。

  1. 协调器(Reconciler)执行 Diff 算法,生成 Fiber 树:
  2. 调度器触发更新任务后,协调器(Reconciler)会启动工作,从组件的根节点开始,基于 Fiber 架

构遍历组件树(可中断遍历)。

  1. 在遍历过程中,协调器会合并更新队列中的所有状态更新,计算出组件的最终新状态,并对比新

旧虚拟 DOM(Diff 算法),找出组件树中需要更新的部分,生成新的 Fiber 树(Fiber 树是

全站分卷索引 · 28 卷
  1. 1第 1 卷
  2. 2第 2 卷
  3. 3第 3 卷
  4. 4第 4 卷
  5. 5第 5 卷
  6. 6第 6 卷
  7. 7第 7 卷
  8. 8第 8 卷
  9. 9第 9 卷
  10. 10第 10 卷
  11. 11第 11 卷
  12. 12第 12 卷
  13. 13第 13 卷
  14. 14第 14 卷
  15. 15第 15 卷
  16. 16第 16 卷
  17. 17第 17 卷
  18. 18第 18 卷
  19. 19第 19 卷
  20. 20第 20 卷
  21. 21第 21 卷
  22. 22第 22 卷
  23. 23第 23 卷
  24. 24第 24 卷
  25. 25第 25 卷
  26. 26第 26 卷
  27. 27第 27 卷
  28. 28第 28 卷