一 React底层原理
React 底层原理
至此我们介绍了 react 的理念,如何解决 cpu 和 io 的瓶颈,关键是实现异步可中断的更新
我们介绍了 react 源码架构(ui=fn(state)),从 scheduler 开始调度(根据过期事件判断优先级,时间分片),经过 render 阶段的深度优先遍历形成 effectList(中间会执行 reconcile|diff),交给 commit 处理真实节点(中间穿插生命周期和部分 hooks),而这些调度的过程都离不开 Fiber 的支撑,Fiber 是工作单元,也是节点优先级、更新 UpdateQueue、节点信息的载体,Fiber 双缓存则提供了对比前后节点更新的基础。我们还介绍了 jsx 是 React.createElement 的语法糖。Lane 模型则提供了更细粒度的优先级对比和计算,这一切都为 concurrent mode 提供了基础,在这之上变可以实现 Suspense 和 batchedUpdate(16、17 版本实现的逻辑不一样),context 的 valueStack 和 valueCursor 在整个架构中运行机制,最后介绍了新版事件系统,包括事件生产、监听和触发
1. react 的架构
https://juejin.cn/post/7211072055780573221#heading-0
https://juejin.cn/post/6926432527980691470
作为架构来说,之前React15
的Reconciler
采用递归的方式执行,数据保存在递归调用栈中,所以被称为stack Reconciler
。React16
的Reconciler
基于Fiber节点
实现,被称为Fiber Reconciler
。
新版 React 架构分为三大部分:
- Scheduler 调度器: 排序优先级,让优先级高的任务先进行 reconcile
- Reconciler 协调器:接受更新,创建虚拟 dom 树,找出哪些节点发生了改变,并打上不同的 effectTag
- Renderer 渲染器:将 Reconciler 中打好标签的节点渲染到视图上
Fiber 这种数据结构后,能完成哪些事情呢,
- 工作单元 任务分解 :Fiber 最重要的功能就是作为工作单元,保存原生节点或者组件节点对应信息(包括优先级),这些节点通过指针的形似形成 Fiber 树
- 增量渲染:通过 jsx 对象和 current Fiber 的对比,生成最小的差异补丁,应用到真实节点上
- 根据优先级暂停、继续、排列优先级:Fiber 节点上保存了优先级,能通过不同节点优先级的对比,达到任务的暂停、继续、排列优先级等能力,也为上层实现批量更新、Suspense 提供了基础
- 保存状态:因为 Fiber 能保存状态和更新的信息,所以就能实现函数组件的状态更新,也就是 hooks
- https://songhaoyong.blogspot.com/2023/07/react.html
2. render 阶段
render:Reconciler 工作的阶段被称为 render 阶段。因为在该阶段会调用组件的 render 方法
render 阶段的主要工作是构建 Fiber 树和生成 effectList
开始工作前会先找到 div#root 对应的rootFiber,称为
hostRootFiber,然后开始生成
wip Fiber 树。 这个过程分为两个部分:beginWork
和 completeWork。 这是一个深度优先遍历的过程。
beginwork
主要的工作是创建或复用子 fiber 节点(同时涉及到 diff 算法,为结点打上 effectTags)
从根节点 rootFiber 开始,遍历到叶子节点,每次遍历到的节点都会执行 beginWork,并且传入当前 Fiber 节点,然后创建或复用它的子 Fiber 节点,并赋值给 workInProgress.child。
completework
主要工作是处理 fiber 的 props、创建 dom、创建 effectList
1、(处理 props 将变化的部分賦值给 workInProgerss.updateQueue)
2、mount 时 调用 createInstance 创建 dom,将后代 dom 节点插入刚创建的 dom 中
3、将 effectTag 的节点,加入到 effectList 中
当遍历到子节点后,会执行 completeWork 方法,执行完成之后会判断此节点的兄弟节点存不存在,如果存在就会为兄弟节点执行 completeWork,当全部兄弟节点执行完之后,会向上回到父节点执行 completeWork,直到 rootFiber。
shouldYiled 方法就是判断待处理的任务队列有没有优先级更高的任务,有的话就先处理那边的 fiber,这边的先暂停一下。
3. commit 阶段
commit:Renderer 工作的阶段被称为 commit 阶段。commit 阶段会把 render 阶段提交的信息渲染在页面上
遍历 render 阶段生成的 effectList,effectList 上的 Fiber 节点保存着对应的 props 变化。之后会遍历 effectList 进行对应的 dom 操作和生命周期、hooks 回调或销毁函数。
commit 阶段的主要工作(即 Renderer 的工作流程)分为三部分:
- before mutation 阶段(执行 DOM 操作前)
这个阶段 DOM 节点还没有被渲染到界面上去,过程中会触发 getSnapshotBeforeUpdate
,也会处理 useEffect
钩子相关的调度逻辑。
- mutation 阶段(执行 DOM 操作)
这个阶段负责 DOM 节点的渲染。在渲染过程中,会遍历 effectList,根据 effectTag 的不同,执行不同的 DOM 操作。
layout 阶段(执行 DOM 操作后)
这个阶段处理 DOM 渲染完毕之后的收尾逻辑。比如调用
componentDidMount/componentDidUpdate
,调用useLayoutEffect
钩子函数的回调等。除了这些之外,它还会把 fiberRoot 的 current 指针指向 workInProgress Fiber 树。
4. render 函数
legacy 模式
render 调用 legacyRenderSubtreeIntoContainer,作用是
1、创建 FiberRootNode 和 rootFiber 节点, (调用 createRootImpl,其会调用到 createFiberRoot 创建 fiberRootNode,然后调用 createHostRootFiber 创建 rootFiber)
2、调用 updateContainer 创建创建 Update 对象挂载到 updateQueue 的环形链表上,(然后执行 scheduleUpdateOnFiber 调用 performSyncWorkOnRoot 进入 render 阶段和 commit 阶段)
concurrent 模式:
调用 ReactDOMRoot.prototype.render 执行 updateContainer,调用 updateContainer 创建创建 Update 对象挂载到 updateQueue 的环形链表上,(然后 scheduleUpdateOnFiber 异步调度 performConcurrentWorkOnRoot 进入 render 阶段和 commit 阶段)
5. fiber
Fiber 是一个 js 对象,能承载节点信息、优先级、updateQueue,同时它还是一个工作单元。
- 工作单元 任务分解 :Fiber 最重要的功能就是作为工作单元,保存原生节点或者组件节点对应信息(包括优先级),这些节点通过指针的形似形成 Fiber 树
- 增量渲染:通过 jsx 对象和 current Fiber 的对比,生成最小的差异补丁,应用到真实节点上。(
fiber
将react
中的渲染任务拆分到每一帧) - 根据优先级暂停、继续、排列优先级:Fiber 节点上保存了优先级,能通过不同节点优先级的对比,达到任务的暂停、继续、排列优先级等能力,也为上层实现批量更新、Suspense 提供了基础
- 保存状态:因为 Fiber 能保存状态和更新的信息,所以就能实现函数组件的状态更新,也就是 hooks
6. hooks 的实现原理
在函数式组件中,hooks 的实现就是基于 fiber 的,多个 hook 会形成 hook 链表,保存在 Fiber 的 memoizedState 的上。hook 不能写在条件判断中正因为 hook 会按顺序存储在链表中,如果 hook 写在条件判断中,就没法保持链表的顺序,会造成乱序。
hook 调用入口
在 hook 源码中 hook 存在于 Dispatcher 中,Dispatcher 就是一个对象,不同 hook 调用的函数不一样,全局变量 ReactCurrentDispatcher.current 会根据是 mount 还是 update 赋值为 HooksDispatcherOnMount 或 HooksDispatcherOnUpdate。
usestate 的工作原理
useState 分为 onMount 和 upDate 两种情况,通过全局变量 ReactCurrentDispatcher.current 来判断。
onMount 时,hook.memoizedState 记录初始的值
update 更新时:
其中 hooks 的memoizedState
是用来记录这个useState
应该返回的结果的,而next
指向的是下一次useState
对应的`Hook 对象。
memoizedState 装着 state,而 dispatchAction 就是 setState
dispatchAction 创建新的 update 对象,将这些更新对象放到 hook.queue.pending 的环形链表中,最后重新渲染 app
hooks 的数据结构
1 | const hook: Hook = { |
7、react 的状态更新流程
触发更新后,会在函数 createUpdate 中创建 update 更新,并将其加入到 updateQueue 中,会从触发更新的节点开始向上遍历到 rootFiber,遍历的过程会处理节点的优先级。然后根据优先级调度 render 阶段的入口函数。最后进行 render 阶段和 commit 阶段。