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

作为架构来说,之前React15Reconciler采用递归的方式执行,数据保存在递归调用栈中,所以被称为stack ReconcilerReact16Reconciler基于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 的对比,生成最小的差异补丁,应用到真实节点上。(fiberreact中的渲染任务拆分到每一帧)
  • 根据优先级暂停、继续、排列优先级: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
2
3
4
5
6
7
const hook: Hook = {
memoizedState: null,//对于不同hook,有不同的值
baseState: null,//初始state
baseQueue: null,//初始queue队列
queue: null // { pending:这是一个链表储存着update},//需要更新的update
next: null,//下一个hook
};

7、react 的状态更新流程


触发更新后,会在函数 createUpdate 中创建 update 更新,并将其加入到 updateQueue 中,会从触发更新的节点开始向上遍历到 rootFiber,遍历的过程会处理节点的优先级。然后根据优先级调度 render 阶段的入口函数。最后进行 render 阶段和 commit 阶段。