var next = beginWork(current, workInProgress, nextRenderExpirationTime);
if (next === null) { // 表示workInProgress为叶节点,无next了,子节点只是text了。
// If this doesn't spawn new work, complete the current work.
next = completeUnitOfWork(workInProgress);
}
return next;
前言
本文中
workInProgress
指的是正在工作的fiber
。"更新队列"指
workInProgress.updateQueue
流程
jsx => element tree => fiber tree => html dom
React
渲染页面schedule work
执行虚拟
DOM
(fiber
树)的更新,有scheduleWork
,requestWork
,performWork
是三部曲。reconcile work
render/reconciliation
):在这个阶段React
会更新数据生成新的Virtual DOM
,然后通过Diff
算法,快速找出需要更新的元素,放到更新队列中去,得到新的更新队列。(改变pendingState
,pendingProps
,最后构建新的fiber tree
)。在初始时则是生成子fiber
,连接fiber
与siblingFiber
commit
):这个阶段React
会遍历更新队列,将其所有的变更一次性更新到DOM
上。并触发响应生命周期函数钩子。流程图
什么是JSX
JSX
是JavaScript
的一种语法扩展,它很像一种模板语言JSX
应用实例:JSX
作为表达式生命周期
componentWillReceiveProps在props改变时执行
JSX的编译方式
在
react
中,通过babel
可以将JSX
编译成JavaScript
代码比如
JSX
表达式编译成
JavaScript
child
以参数形式一个个传入React.createElement
(而不是以数组形式)编译得到
element
的流程如下:在得到首个
element
后,便能执行ReactDOM.render
函数,并将element,container,callback
传入1.
schedule work
阶段1.1 renderSubtreeIntoContainer
渲染一个元素到
DOM
,将container
中原有的子元素删除。比如下列代码,会将原有的p
元素删除,而用<div>child</div>
代替待新的
root
构建好后,将之与container
关联:root = container._reactRootContainer = newRoot
1.2 createContainer
创建
fiber root
1.3 scheduleTopLevelUpdate
创建
update
对象,初始的fiber root
的updateQueue
中,partialState
是element
组成的对象1.4 insertUpdateIntoFiber
将
update
对象植入current fiber
中。挂载在first
和last
属性上1.5 scheduleWorkImpl
用一个
while
循环沿着parent
位置向上遍历fiber
,直到root.current
,并且更新每个fiber
的expirationTime
然后再
requestWork
1.6 requestWork
当调度器有
update
工作的时候,就会调用requestWork
。在其中改变firstScheduledRoot
,lastScheduledRoot
等被调度的FiberRoot
。函数中需要判断的
isBatchingUpdates
,在事件触发后会改变。以下代码是事件触发后会执行,所以在requestWork
时,isBatchingUpdates
为true
事件绑定机制
1.7 performWork
找到优先级最高的
work
,并激活它,此后开始构建fiber
树1.8 performWorkOnRoot
开始渲染,
isRendering = true
区分同步渲染还是异步渲染在
performWorkOnRoot
方法中有两个重要的步骤。1.renderRoot
。2.commitRoot
。2.
reconcile work
阶段详情见:Reconciliation
2.1 renderRoot
开始
work
,设置workloop
函数中所用到的nextUnitOfWork
(复制nextRoot.current
得到) ,然后执行workloop
循环函数,得到更新后的fiber
树。如下图:创建
workInProgress
(复制current fiber
(这也是current.alternate
和current
不全等的原因)),并将workInProgress
的alternate
指向current fiber
,在之后使用workInProgress
的时候,都是用它的替代品(current.alternate
)来作文章所以在
reconcileChildren
阶段,改变了workInProgress.child
,但是current.child
不会改变。也就是workInProgress
是current
的复制品,所以workInProgress
的属性改变并不会导致current
属性改变。经过
renderRoot
后,得到root.current.alternate
2.1.1 workloop
以深度遍历方式,激活所有期望
work
,对每个work
都执行performUnitOfWork
实现深度遍历:
遍历逻辑如下:
对1中提到的
workInProgress
或者说nextUnitOfWork
开始工作,对下面层级的子fiber
进行更新。2.1.2 performUnitOfWork
performUnitOfWork
中有两个重要函数,beginWork
和completeUnitOfWork
。2.1.2.1 beginWork
期间执行生命周期函数
componentWillMount
,componentWillReceiveProps
,componentWillUpdate
。在updateClassComponent
中执行render
根据
workInProgress
的tag
(component
类型)来做出相应的更新。将更新后得到的child
(可为null
)返回,置为child
属性,此时alternate
仍未null
在
bailoutOnAlreadyFinishedWork
函数中,可以改变workInProgress.child
及workInProgress.child.alternate
childFiber.alternate
改变是在cloneChildFibers
过程,将本来为null
的alternate
改为指向childFiber
有如下
update
操作:如果
workInProgress
更新队列(如1.4中的updateQueue
)不为null
,则处理更新队列。在处理过程中将callback
回调函数推进更新队列的callbackList
,并将update
对象链的partialState
合并。设置
workInProgress.effectTag
,标记commit
阶段的操作(插入、更新、插入并更新、删除)给
workInProgress
挂载child fiber
,并返回该child
。在处理child
的过程中,需要创建fiber
,有这几种方式createFiberFromFragment,createFiberFromFragment,useFiber
如果workInProgress 更新队列(如1.4中的updateQueue)为,则返回 null。则返回
workInProgress.child
此方法为更新形如
div、p
标签之类的html
组件。故在reconcileChildren
时传入的是props.children
。如果props.children
是文本字符串而不是element
,则改变workInProgress
的effectTag
以便在commit
阶段重置内容,并且传入reconcileChildFibers的newChild
为null
,并且最后返回null
。如果
props.children
为数组,则在reconcileChildrenArray
中按序给每个元素进行调和,用sibling
将每个fiber
连接起来。最后返回第一个child
。在更新时
performWork
的过程中,若新旧props
没有改变,则直接返回workInProgress.child
2.1.2.2 completeUnitOfWork
如果
beginWork
返回的child
为null
,则证明workInProgress
已没有孩子fiber
了,这时就需要去找workInProgress
的下一个可遍历fiber
节点。可以是返回sibling fiber
,没有的话是找父级的sibling fiber
并返回。在此函数的
while
循环中,如果workInProgress.tag
大于PerformedWork
所对应的值,设置returnFiber
的effect
串,在下一次循环时,将此fiber
的effect
串又传递给returnFiber
completeWork
pendingProps
挂到memoizedProps
workInProgress
的tag
来标记commit
阶段如何操作,如改变effectTag
effect
串,如firstEffect,lastEffect,nextEffect
instance
实例,并添加子节点diff
算法2.2 commitRoot
在
renderRoot
后构建了一个新的fiber
树,但此树只是root.current
的替代品,还没有真正的应用到root.current
上。但是当
commitRoot
后,将root.current
改变为finishedWork
。这时root.current.alternate === finishedWork.alternate
。为最初的currentFiber
对象.在
commitRoot
中,将已经连接起来的effect
串从头开始执行,对于effect fiber
,根据其effectTag
,来标记其操作方式。当渲染
dom
时,由组件为单位,从里到外渲染。2.2.1 commitAllHostEffects
详情见: 视图改变
向
parent
插入真实dom
节点2.2.2 commitAllLifeCycles
根据
effect fiber
的effectTag
和alternate
来决定钩子函数如何执行。可执行componentDidMount
、componentDidUpdate
关于
currentFiber.alternate
的改变下列示范代码,详细请看源码的
createWorkInProgress