react底层原理

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

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的数据结构

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阶段。


评论

此博客中的热门博文

vue原理

Git操作