We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
本文讲的主要部分是 从 ReactDOM.render 调用到 fiber tree 创建之间的准备步骤,主要包括了 ReactRoot的创建,优先级以及同异步渲染方式的确定还有 FiberTreeRoot 的准备。
ReactDOM.render
fiber tree
ReactRoot
FiberTreeRoot
在开始之前,我们要先知道一个事情就是:平时我们写React,都要引入多一个 'react-dom',用 ReactDOM.render来把React生成的ReactElement渲染到 DOM Tree 中。
ReactElement
也就是说我们引入的React负责ReactElement组件相关的东西,渲染层面抽出来交给,ReactDOM、 ReactNative,这种分离这也赋予了React跨终端渲染的能力。
我们先来写个小demo,后面顺着这个demo断点来看代码会容易很多,本文以第一次渲染的情况做为探讨的情况(这也意味着这个阶段不会进行diff的阶段,后面可能会把diff当一个点抽出来说)。
首先我们看一下 React.render(<T />, document.getElementById('container')),这里面的<T />首先会被babel调用 React.createElement生成为ReactElement,具体可以看第二篇 React源码系列(二): 从jsx到createElement
React.render(<T />, document.getElementById('container'))
<T />
React.createElement
ReactDOM.render 首先会调用 legacyCreateRootFromDOMContainer 创建一个ReactRoot的实例,我们叫他root。
legacyCreateRootFromDOMContainer
root
这个 root 有什么用呢? 我们可以看到 ReactRoot原型链上有 render 方法,这个方法会开始执行我们root的整个render操作。 _internalRoot 属性指向一个 root obj,挂载着我们后面渲染所需的一些基本信息,比如 current 指向我们的 fiber tree的根节点 HostRoot, containerInfo 指向 dom容器,也就是本例中的 document.getElementById('container')。
render
_internalRoot
root obj
current
containerInfo
document.getElementById('container')
这些属性创建绑定大都可以在createFiberRoot找到。创建完ReactRoot之后,调用原型链上的ReactRoot.render方法。
createFiberRoot
ReactRoot.render
ReactRoot.render 会先调用 requestCurrentTime 获取当前已经花费的时间, 然后调用computeExpirationForFiber来确定我们的优先级, computeExpirationForFiber 会根据我们当前的工作类型,比如isWorking, isCommit, isAsync 等等来返回对应的优先级,第一次渲染默认都是Sync的情况。如果不是同步渲染的情况,既Interactive 或者 Async,这时候会根据我们 前面requestCurrentTime获取的已花费时间 调用相应的算法来确定优先级。
requestCurrentTime
computeExpirationForFiber
动态获取异步、交互式优先级算法
获取优先级函数
这里我们就可以看出React的更新优先级: Sync、Interactive、Async加上offscreen只留意到有四种。 但是之前看到网上很多说有六种情况,应该是我哪里漏了或者还没抽象成这种可以执行优先级的层面。
网上看到的六种更新情况 synchronous 与之前的Stack reconciler操作一样,同步执行 task 在next tick之前执行 animation 下一帧之前执行 high 在不久的将来立即执行 low 稍微延迟(100-200ms)执行也没关系 offscreen 下一次render时或scroll时才执行
获取该任务的优先级之后,给ReactRoot设置一个默认的context。
根据优先级之后创建一个update任务,这个update对象的payload指向我们的组件,既这个update的任务是处理我们的组件,callback指向一个ReactWork._onCommit,实际上就是执行完render之后调用我们在ReactDOM.render传入的回调函数。
ReactWork._onCommit
把上述update任务加到fiber的更新队列中(fiber.updateQueue),后面会在某个阶段统一处理updateQueue。
fiber.updateQueue
updateQueue
到这一步,我们已经完成了HostRoot(fiber tree root)的创建、context的设置、优先级(更新模式异步还是高优先级还是低优先级)和更新队列的设置。
在第一阶段,我们更新完 fiber的 updateQueue之后,就调用 scheduleWork 开始调度这次的工作。
scheduleWork
scheduleWork 主要的事情就是找到我们要处理的 root设置刚才获取到的执行优先级,然后调用 requestWork。
requestWork
requestWork的工作很简单,就是维护一条 scheduledRoot 的单向链表,比如说 lastScheduleRoot == null,意味着我们当前已经没有要处理的 root,这时候就把 firstScheduleRoot、lastScheduleRoot、root.nextScheduleRoot 都设置为 root。如果 lastScheduleRoot !== null,则把 lastScheduledRoot.nextScheduledRoot设置为root,等 lastScheduledRoot调度完就会开始处理当前 root。
scheduledRoot
lastScheduleRoot == null
firstScheduleRoot、lastScheduleRoot、root.nextScheduleRoot
lastScheduleRoot !== null
lastScheduledRoot.nextScheduledRoot
lastScheduledRoot
然后就根据执行方式(是否是同步的方式)传不同的参数调用performWork,设置isRendering开关,开始render工作。
performWork
isRendering
到这里我们算是Render的第二个阶段,设置调度的root,优先级,异步同步模式,接下来就是根据这些开关和模式,调用RenderRoot去执行fiber tree 的创建和对应的操作
RenderRoot会根据 current 拷贝创建另外一条单向链表 workInProgress 作为工作的Fiber,后面的工作都会在workInProgress上面进行,而不是current。为什么呢?
然后调用workLoop进行create fiber、diff 等操作。
workLoop 顾名思义,就是不断循环工作。判断有没有nextUnitOfWork,既下一个工作单元,我们都知道fiber系统给予React渲染阶段可中断的特性,所以这里每次都是只执行一个工作单元,而不是像React15一样一把梭,调用performUnitOfWork去处理这个工作单元。假如我们这次的work是可以被中断的,既如果不是Sync同步的情况,那么每一次循环调用performUnitOfWork之前,会调用shouleYield来判断是否有时间继续执行,有的话就继续执行,没有就等下一次有空闲时间再执行。
nextUnitOfWork
performUnitOfWork
Sync同步
shouleYield
performUnitOfWork 调用 beginWork,如果beginWork返回null,意味着我们这次工作已经做完了,这时候就调用 completeUnitOfWork ,开始commit的操作。
beginWork
completeUnitOfWork
commit
The text was updated successfully, but these errors were encountered:
请问 requestWork的工作很简单,就是维护一条 scheduledRoot 的单向链表,比如说 lastScheduleRoot == null,意味着我们当前已经没有要处理的 root,这时候就把 firstScheduleRoot、lastScheduleRoot、root.nextScheduleRoot 都设置为 root。如果 lastScheduleRoot !== null,则把 lastScheduledRoot.nextScheduledRoot设置为root,等 lastScheduledRoot调度完就会开始处理当前 root。 这么一条单链表有什么意义啊?first last roo.next都是root
Sorry, something went wrong.
No branches or pull requests
本文讲的主要部分是 从
ReactDOM.render
调用到fiber tree
创建之间的准备步骤,主要包括了ReactRoot
的创建,优先级以及同异步渲染方式的确定还有FiberTreeRoot
的准备。对一些比较经典的东西可能会简单带过,因为目前主要是梳理整个 React 的运行机制,而关于某一些细点,后面可能会独立出来说会比较清晰。
在开始之前,我们要先知道一个事情就是:平时我们写React,都要引入多一个 'react-dom',用
ReactDOM.render
来把React生成的ReactElement
渲染到 DOM Tree 中。也就是说我们引入的React负责ReactElement组件相关的东西,渲染层面抽出来交给,ReactDOM、 ReactNative,这种分离这也赋予了React跨终端渲染的能力。
我们先来写个小demo,后面顺着这个demo断点来看代码会容易很多,本文以第一次渲染的情况做为探讨的情况(这也意味着这个阶段不会进行diff的阶段,后面可能会把diff当一个点抽出来说)。
首先我们看一下
React.render(<T />, document.getElementById('container'))
,这里面的<T />
首先会被babel调用React.createElement
生成为ReactElement,具体可以看第二篇 React源码系列(二): 从jsx到createElement第一阶段 准备ReactRoot和后续需要的基本属性
ReactRoot
ReactDOM.render 首先会调用
legacyCreateRootFromDOMContainer
创建一个ReactRoot
的实例,我们叫他root
。这个
root
有什么用呢? 我们可以看到 ReactRoot原型链上有render
方法,这个方法会开始执行我们root的整个render操作。_internalRoot
属性指向一个root obj
,挂载着我们后面渲染所需的一些基本信息,比如current
指向我们的 fiber tree的根节点 HostRoot,containerInfo
指向 dom容器,也就是本例中的document.getElementById('container')
。这些属性创建绑定大都可以在
createFiberRoot
找到。创建完ReactRoot之后,调用原型链上的ReactRoot.render
方法。scheduleRootUpdate 调度Root上的更新操作
ReactRoot.render
会先调用requestCurrentTime
获取当前已经花费的时间, 然后调用computeExpirationForFiber
来确定我们的优先级,computeExpirationForFiber
会根据我们当前的工作类型,比如isWorking, isCommit, isAsync 等等来返回对应的优先级,第一次渲染默认都是Sync的情况。如果不是同步渲染的情况,既Interactive 或者 Async,这时候会根据我们 前面requestCurrentTime获取的已花费时间 调用相应的算法来确定优先级。动态获取异步、交互式优先级算法

获取优先级函数

这里我们就可以看出React的更新优先级: Sync、Interactive、Async加上offscreen只留意到有四种。 但是之前看到网上很多说有六种情况,应该是我哪里漏了或者还没抽象成这种可以执行优先级的层面。
获取该任务的优先级之后,给ReactRoot设置一个默认的context。
根据优先级之后创建一个update任务,这个update对象的payload指向我们的组件,既这个update的任务是处理我们的组件,callback指向一个
ReactWork._onCommit
,实际上就是执行完render
之后调用我们在ReactDOM.render
传入的回调函数。把上述update任务加到fiber的更新队列中(
fiber.updateQueue
),后面会在某个阶段统一处理updateQueue
。到这一步,我们已经完成了HostRoot(fiber tree root)的创建、context的设置、优先级(更新模式异步还是高优先级还是低优先级)和更新队列的设置。
第二阶段 准备调度需要的优先级,渲染模式(异步同步),需要渲染的Root等
在第一阶段,我们更新完 fiber的 updateQueue之后,就调用
scheduleWork
开始调度这次的工作。scheduleWork 调度工作(准备工作)
scheduleWork
主要的事情就是找到我们要处理的root
设置刚才获取到的执行优先级,然后调用requestWork
。requestWork 申请工作 处理scheduleRoot单向链表,根据执行方式调用 performWork
requestWork的工作很简单,就是维护一条
scheduledRoot
的单向链表,比如说lastScheduleRoot == null
,意味着我们当前已经没有要处理的 root,这时候就把firstScheduleRoot、lastScheduleRoot、root.nextScheduleRoot
都设置为 root。如果lastScheduleRoot !== null
,则把lastScheduledRoot.nextScheduledRoot
设置为root,等lastScheduledRoot
调度完就会开始处理当前 root。然后就根据执行方式(是否是同步的方式)传不同的参数调用
performWork
,设置isRendering
开关,开始render工作。到这里我们算是Render的第二个阶段,设置调度的root,优先级,异步同步模式,接下来就是根据这些开关和模式,调用RenderRoot去执行fiber tree 的创建和对应的操作
第三阶段 创建一条用于工作的fiber workInProgress,处理同步异步控制,调用beginWork
RenderRoot
RenderRoot会根据
current
拷贝创建另外一条单向链表 workInProgress 作为工作的Fiber,后面的工作都会在workInProgress上面进行,而不是current。为什么呢?然后调用workLoop进行create fiber、diff 等操作。
workLoop
workLoop 顾名思义,就是不断循环工作。判断有没有
nextUnitOfWork
,既下一个工作单元,我们都知道fiber系统给予React渲染阶段可中断的特性,所以这里每次都是只执行一个工作单元,而不是像React15一样一把梭,调用performUnitOfWork
去处理这个工作单元。假如我们这次的work是可以被中断的,既如果不是Sync同步
的情况,那么每一次循环调用performUnitOfWork
之前,会调用shouleYield
来判断是否有时间继续执行,有的话就继续执行,没有就等下一次有空闲时间再执行。performUnitOfWork
调用beginWork
,如果beginWork
返回null,意味着我们这次工作已经做完了,这时候就调用completeUnitOfWork
,开始commit
的操作。The text was updated successfully, but these errors were encountered: