从编写组件到最后屏幕生成界面,如上图所示,我们现在需要知道的就是后面几步是如何运行的。
概述
这张图解释了 React 渲染过程的几个阶段:
- 渲染触发:通过更新某处的状态来触发渲染。
- 渲染阶段:React 调用组件函数,确定如何更新 DOM。这是内部计算阶段,不会立即对屏幕产生视觉变化。
- 提交阶段:React 实际写入 DOM,进行元素的更新、插入和删除。
- 浏览器绘制:浏览器将更新后的内容绘制到屏幕上。
在 React 中,渲染并不是指更新 DOM 或在屏幕上显示元素。渲染仅在 React 内部发生,不会直接产生视觉变化。
渲染触发
渲染触发条件
- 初次渲染:应用程序的初始渲染。
- 状态更新:一个或多个组件实例的状态更新(重新渲染)。
直觉上的状态更新如下:
但其实并不是,实际上它是批量更新的。
例子如下:
关键点
- 渲染过程是为整个应用程序触发的。
- 实际上,看起来 React 只重新渲染状态更新的组件,但背后工作并非如此。
- 渲染不会立即触发,而是安排在 JavaScript 引擎有空闲时间时执行。事件处理程序中的多个
setState
调用也会进行批处理。
渲染阶段
什么是虚拟DOM?
它只是一个javascript对象。
为什么需要协调(Reconciliation)和区别(diffing)?
纤维树(Fiber tree)
React在内部维护了一个Fiber tree,用于存储每个组件的状态。当函数组件重新执行时,React会根据这个链表来恢复状态,而不是重新初始化。
- React 元素树(虚拟 DOM):左侧显示了一个 React 组件树,包含组件如
App
、Video
、Modal
、Overlay
、h3
和button
。这是 React 的虚拟 DOM 表示。 - Fiber 树:右侧是 Fiber 树,它在初次渲染时被创建。每个组件实例和 DOM 元素都有一个对应的“fiber”。
- Fiber:每个 fiber 是一个“工作单元”,包含以下信息:
- 当前状态
- 属性(Props)
- 副作用
- 使用的钩子(hooks)
- 工作队列
- 纤维树的特点:
- 内部树结构:每个组件实例和DOM元素都有一个对应的“fiber”。这些fiber节点形成了一个树状结构,用于描述UI界面。
- Fiber节点不在每次渲染时重新创建:这意味着React可以复用已有的fiber节点,从而减少不必要的计算和内存使用。
-
异步渲染
- 异步完成工作:Fiber允许将渲染任务分成小块,利用浏览器的空闲时间进行处理。这种方式避免了长时间阻塞主线程,提高了应用的响应性。
- 任务优先级调度:React可以根据任务的重要性来决定执行顺序,优先处理用户交互等关键任务。
-
并发特性
- 支持并发特性:Fiber架构支持React中的并发模式,使得应用能够更好地处理复杂交互和动态变化,例如Suspense或过渡效果。
- 长时间渲染不会阻塞JavaScript引擎:通过将渲染过程分割成小块,React能够在不中断用户界面的情况下进行更新。
-
具体实现
- 增量式渲染:通过将渲染任务分配到多个帧中,React可以保持应用的流畅性,即使在复杂布局或大量计算时也能保持响应。
- 可中断和恢复的渲染:在需要时,React可以暂停当前任务,并在稍后恢复,从而提高用户体验。
协调(Reconciliation)举例
区分(Diffing)举例
key prop是什么?
在list中使用key prop
使用key prop重置状态
这不是我们想要的,需要使用key prop进行重置state。
提交阶段
绘制阶段
总结
当React组件的state
发生变化时,会触发组件的重新渲染。以下是详细过程:
1. 状态更新
当你调用setState
或useState
的更新函数时,React会将新的状态值存入组件的状态中,并标记该组件为“需要更新”。
2. 调度重新渲染
React会将需要更新的组件加入更新队列,并安排一次重新渲染。React可能会批量处理多个状态更新,以减少不必要的渲染。当React调度重新渲染时,函数组件的内容会被重新执行一遍,在执行到如useState、useReducer、useContext等hook的时候,不会重新初始化,而是从 React 的内部存储中恢复之前的值。
3. 生成新的虚拟DOM
React会调用组件的render
方法(类组件)或函数组件本身,生成新的虚拟DOM树。
4. Diff算法比较
React使用Diff算法比较新旧虚拟DOM树,找出需要更新的部分。比较规则包括:
-
如果元素类型不同,直接替换整个子树。
-
如果元素类型相同,更新变化的属性。
-
对于列表元素,通过
key
属性识别元素,减少不必要的更新。
5. 更新真实DOM
React将虚拟DOM的差异应用到真实DOM上,只更新必要的部分,而不是重新渲染整个DOM树。
6. 调用生命周期方法或Hooks
-
类组件:
-
如果实现了
shouldComponentUpdate
,React会调用它来决定是否跳过渲染。 -
更新后会调用
componentDidUpdate
。
-
-
函数组件:
-
如果使用了
useEffect
,React会根据依赖项决定是否执行副作用。
-
7. 完成渲染
更新完成后,React会清理临时数据,并等待下一次状态或属性变化。
示例代码
function Counter() {
const [count, setCount] = React.useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
-
点击按钮时,
setCount
会更新count
状态。 -
React会重新渲染
Counter
组件,生成新的虚拟DOM。 -
通过Diff算法,React发现只有
<p>
标签的内容发生了变化,因此只更新真实DOM中的文本内容。
性能优化
-
避免不必要的渲染:
-
类组件:使用
shouldComponentUpdate
或React.PureComponent
。 -
函数组件:使用
React.memo
。
-
-
减少状态更新频率:
-
使用
useCallback
和useMemo
缓存函数和值。
-