同步更新过程的局限
在v16版本以前,react的更新过程是通过递归从根组件树开始同步进行的,更新过程无法被打断,当组件树很大的时候就会出现卡顿的问题
react中的虚拟dom
import React, { Component } from 'react';
export default class ClickCounter extends Component{
constructor(props) {
super(props);
this.state = {count: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState((state) => {
return {count: state.count + 1};
});
}
render() {
return [
<button key="1" onClick={this.handleClick}>Update counter</button>,
<span key="2">{this.state.count}</span>
]
}
}
在经过jsx编译器编译后
class ClickCounter {
...
render() {
return [
React.createElement(
'button',
{
key: '1',
onClick: this.onClick
},
'Update counter'
),
React.createElement(
'span',
{
key: '2'
},
this.state.count
)
]
}
}
最终转换成的虚拟dom
[
{
$$typeof: Symbol(react.element),
type: 'button',
key: "1",
props: {
children: 'Update counter',
onClick: () => { ... }
}
},
{
$$typeof: Symbol(react.element),
type: 'span',
key: "2",
props: {
children: 0
}
}
]
fiber
requestIdleCallback
requestIdleCallback((deadline)=>{
deadline.timeRemaining() 一帧中剩余的时间
deadline.didTimeout 是否已超时timeout
timeout 设置当n毫秒后,强制执行任务
},timeout);
和requestAnimation的区别
实现空闲时间暂停、恢复任务的基本实现
function myNonEssentialWork (deadline) {
// 当回调函数是由于超时才得以执行的话,deadline.didTimeout为true
while ((deadline.timeRemaining() > 0 || deadline.didTimeout)
{
if(tasks.length > 0){
doWorkIfNeeded();
}
}
//如果当前帧没有执行完任务,交给下一帧的空闲时间去执行
if (tasks.length > 0) {
requestIdleCallback(myNonEssentialWork);
}
}
react并未使用原生requestIdleCallback,而是使用扩展实现的原因
react在不支持requestIdleCallback的浏览器中,通过requestAnimation+MessageChannel模拟实现
window.myRequestIdleCallback = function(callback,options){
requestAnimationFrame((DOMHighResTimeStamp)=>{
//requestAnimationFrame第一个参数表示执行时的时间戳
//最迟执行时间,当前触发的时间+一帧的时间
myRequestIdleCallback.IdleDeadline = DOMHighResTimeStamp + myRequestIdleCallback.activeAnimationTime
myRequestIdleCallback.peedingCallback = callback;
myRequestIdleCallback.channel.port1.postMessage('start');
})
}
myRequestIdleCallback.activeAnimationTime = 1000/60; // 每一帧的时间 ms
myRequestIdleCallback.IdleDeadline; // 这一帧的截止时间
myRequestIdleCallback.tiemRemaing = ()=> myRequestIdleCallback.IdleDeadline - performance.now(); // 执行到此语句还有多少空余时间剩余
myRequestIdleCallback.channel = new MessageChannel();
myRequestIdleCallback.channel.port2.onmessage = function(event){
// 当收到消息的时候表示,浏览器已经空闲,处理该任务
const currentTime = performance.now();//运行到当前回调函数的时刻
// 如果deadline为true,意味着当前时间已经超出了每一帧的截止时间,也就等同于本帧没有任何时间可以处理回调函数,此帧过期
const isDeadLine = currentTime > myRequestIdleCallback.IdleDeadline
if( !isDeadLine || myRequestIdleCallback.tiemRemaing()>0){
if(myRequestIdleCallback.peedingCallback){
//执行回调并传入一帧的剩余时间
myRequestIdleCallback.peedingCallback({
timeRemaining:myRequestIdleCallback.tiemRemaing
});
}
}
}
引入Fiber架构
React Fiber把更新过程碎片化,每执行完一段更新过程,就把控制权交还给React负责任务协调的模块,看看有没有其他紧急任务要做,如果没有就继续去更新,如果有紧急任务,那就去做紧急任务,做完看是否还有剩余时间,如果有继续下一个任务;如果没有,挂起当前任务,将时间控制权交给主线程,等主线程不忙的时候在继续执行。
通过Fiber的架构,提供了一种跟踪,调度,暂停和恢复的工作
React Fiber一个更新过程被分为两个阶段(Phase)
第一个阶段Reconciliation(协调) Phase和第二阶段Commit Phase,在第一阶段Reconciliation Phase,包含React元素的作用,生命周期方法和渲染方法,以及应用于组件子元素的diff算法等相关内容。这个阶段是可以被打断的;但是到了第二阶段Commit Phase,那就一鼓作气把DOM更新完,绝不会被打断
对生命周期函数的影响
下面这些生命周期函数则会在第一阶段调用:
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
getDerivedStateFromProps
因为第一阶段的过程会被打断而且“重头再来,比如说,一个低优先级的任务A正在执行,已经调用了某个组件的componentWillUpdate函数,接下来发现自己的时间分片已经用完了,于是冒出水面,看看有没有紧急任务,有一个紧急任务B,接下来React Fiber就会去执行这个紧急任务B,任务A虽然进行了一半,但是没办法,只能完全放弃,等到任务B全搞定之后,任务A重头来一遍,注意,是重头来一遍,不是从刚才中断的部分开始,也就是说,componentWillUpdate函数会被再调用一次
下面这些生命周期函数则会在第二阶段调用:
getSnapshotBeforeUpdate
componentDidMount
componentDidUpdate
componentWillUnmount
Reconciliation(协调调和阶段)
在render阶段时,React通过setState或React.render来执行组件的更新,并确定需要在UI中更新的内容。如果是第一次渲染,React会为render方法返回的每个元素,创建一个新的fiber节点。在接下来的更新中,将重用和更新现有React元素的fiber 节点。render阶段的结果是生成一个部分节点标记了side effects的fiber节点树
render阶段可以异步执行。 React可以根据可用时间来处理一个或多个fiber节点,然后停止已完成的工作,并让出调度权来处理某些事件。然后它从它停止的地方继续。但有时候,它可能需要丢弃完成的工作并再次从头。由于在render阶段执行的工作不会导致任何用户可见的更改(如DOM更新),因此这些暂停是不会有问题的。
commit阶段
side effects描述了在下一个commit阶段需要完成的工作。在此阶段,React采用标有side effects的fiber树并将其应用于实例。它遍历side effects列表并执行DOM更新和用户可见的其他更改。在此阶段执行的工作,将会生成用户可见的变化,这就是React需要一次完成它们的原因。
fiber的数据结构
每一个fiber都有最重要的三个属性,return指向父fiber,sibling指向兄弟fiber,child指向第一个子fiber,为了能实现中断后恢复,即记住中断时的状态,将其设计成链表的结构,即能实现每一个fiber单元都能找到其他fiber
fiber的遍历顺序
整个fiber tree采用DFS深度遍历的方式,优先访问第一个child子节点,当child为null时,访问该节点的sibling,然后访问sibling的child子节点,当sibling为null时,通过return返回到父节点继续采用这种方式进行遍历
class App extends React.Component {
render() {
return (
<div className="app">
<header>header</header>
<Content />
<footer>footer</footer>
</div>
);
}
}
class Content extends React.Component {
render() {
return (
<React.Fragment>
<p>1</p>
<p>2</p>
<p>3</p>
</React.Fragment>
);
}
}
export default App;
上述代码首次创建fiber树的遍历顺序为:
首次构建出的fiber树:
遍历所有的 fiber 节点,通过 Diff 算法计算所有更新工作,产出 EffectList 给到 commit 阶段使用
Current tree和WorkInProgress tree
在第一次渲染之后,React最终得到一个fiber tree,它反映了用于渲染UI的应用程序的状态。这棵树通常被称为current tree。当React开始处理更新时,它会构建一个所谓的workInProgress tree,它反映了要刷新到屏幕的未来状态。
每次重新渲染都会重新创建 Element(虚拟dom树), 但是 Fiber 不会,Fiber 只会使用对应的 Element 中的数据来更新自己必要的属性
每个Fiber上都有个alternate属性,也指向一个 Fiber,创建 WorkInProgress 节点时优先取alternate,如果没有的话就新建一个
所有work都在workInProgress tree中的fiber上执行。当React遍历current tree时,对于每个现有fiber节点,它会使用render方法返回的React元素中的数据创建一个备用(alternate)fiber节点,这些节点用于构成workInProgress tree(备用tree)。处理完更新并完成所有相关工作后,React将备用tree刷新到屏幕。一旦这个workInProgress tree在屏幕上呈现,它就会变成current tree。
每个fiber节点都会通过alternate字段保持对另一个树的对应节点的引用。current tree中的节点指向workInProgress tree中的备用节点,反之亦然。
React代码在第一次执行时,因为页面还没有渲染出来,此时是没有current树的,只有一个正在构建DOM的workInProgress树。
当下次更新时,会将Current tree的hostRootFiber拷贝过去
构建workInProgress fiber
//ReactFiber.old.js
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {//区分是在mount时还是在update时
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;//复用属性
workInProgress.type = current.type;
workInProgress.flags = NoFlags;
workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
//...
}
workInProgress.childLanes = current.childLanes;//复用属性
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
const currentDependencies = current.dependencies;
workInProgress.dependencies =
currentDependencies === null
? null
: {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext,
};
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
return workInProgress;
}
在首次mount时会创建fiberRoot和rootFiber
然后根据element对象创建workInProgress Fiber
创建完成后把workInProgress Fiber切换成current Fiber
在update时:会根据新的状态形成的element 和current Fiber对比形(diff算法)成一颗叫workInProgress的Fiber树
然后将fiberRoot的current指向workInProgress树,此时workInProgress就变成了current Fiber。
双缓冲技术
render 的时候有了这么一条单链表,当调用 setState 的时候Diff 得到 change 的方式,采用的是一种叫双缓冲技术(double buffering),这个时候就需要另外一颗树:WorkInProgress Tree,它反映了要刷新到屏幕的未来状态。
current树对应了屏幕上已经渲染好的内容,workInprogress树是根据current树深度优先遍历出来新的fiber树,所有需要更新的内容都会在workInprogress树上体现。当更新未完成时,屏幕上始终都只会显示current树对应的内容,当更新完成则会将current树切换为workInprogress树,此时workInprogress树则变成新的current树。
好处:
Side-effects(effects)
我们可以将React中的一个组件视为一个使用state和props来计算UI的函数。每个其他活动,如改变DOM或调用生命周期方法,都应该被认为是side-effects,应用effects是一种work,fiber节点是一种方便的机制,可以跟踪除更新之外的effects。每个fiber节点都可以具有与之相关的effects, 通过fiber节点中的effectTag字段表示。
因此,Fiber中的effects基本上定义了处理更新后需要为实例完成的工作,对于Host组件(DOM元素),工作包括添加,更新或删除元素。对于class组件,React可能需要更新ref并调用componentDidMount和componentDidUpdate生命周期方法,还存在与其他类型的fiber相对应的其他effects。
Effects list
构建的具有side-effects的fiber节点的链表,没有side-effects的fiber节点并不会在链表中
此链表的目标是标记具有DOM更新或与其关联的其他effects的节点,此列表是finishedWork tree的子集,并使用nextEffect属性指向下一个节点,而不是current和workInProgress树中使用的child属性进行链接。
Dan Abramove为effecs list提供了一个类比: 他喜欢将它想象成一棵圣诞树,“圣诞灯”将所有带有effects的节点绑定在一起。
例如:橙色的节点都有一些effects需要处理,effects list将它们链接在一起,以便React可以在以后跳过其他节点
当遍历节点时,React使用firstEffect指针来确定effects list的开始位置。所以上图可以表示为这样的线性列表
fiberRoot和rootFiber
const fiberRoot = query('#container')._reactRootContainer._internalRoot
const hostRootFiberNode = fiberRoot.current
可以从组件实例中获取单个fiber节点,如下所示:
compInstance._reactInternalFiber
fiber的结构
上述ClickCounter组件
{
stateNode: new ClickCounter,
type: ClickCounter,
alternate: null,
key: null,
updateQueue: null,
memoizedState: {count: 0},
pendingProps: {},
memoizedProps: {},
tag: 1,
effectTag: 0,
nextEffect: null
}
ClickCounter内的span元素
{
stateNode: new HTMLSpanElement,
type: "span",
alternate: null,
key: "2",
updateQueue: null,
memoizedState: null,
pendingProps: {children: 0},
memoizedProps: {children: 0},
tag: 5,
effectTag: 0,
nextEffect: null
}
fiber节点大致结构
fiber.tag: 表示 fiber 类型, 根据ReactElement组件的 type 进行生成, 在 react 内部共定义了25 种 tag.
fiber.key: 和ReactElement组件的 key 一致.
fiber.elementType: 一般来讲和ReactElement组件的 type 一致
fiber.type: 一般来讲和fiber.elementType一致. 一些特殊情形下, 比如在开发环境下为了兼容热更新(HotReloading), 会对function, class, ForwardRef类型的ReactElement做一定的处理, 这种情况会区别于fiber.elementType, 具体赋值关系可以查看源文件.
fiber.stateNode: 与fiber关联的局部状态节点(比如: HostComponent类型指向与fiber节点对应的 dom 节点; 根节点fiber.stateNode指向的是FiberRoot; class 类型节点其stateNode指向的是 class 实例).
fiber.return: 指向父节点.
fiber.child: 指向第一个子节点.
fiber.sibling: 指向下一个兄弟节点.
fiber.index: fiber 在兄弟节点中的索引, 如果是单节点默认为 0.
fiber.ref: 指向在ReactElement组件上设置的 ref(string类型的ref除外, 这种类型的ref已经不推荐使用, reconciler阶段会将string类型的ref转换成一个function类型).
fiber.pendingProps: 输入属性, 从ReactElement对象传入的 props. 用于和fiber.memoizedProps比较可以得出属性是否变动.
fiber.memoizedProps: 上一次生成子节点时用到的属性, 生成子节点之后保持在内存中. 向下生成子节点之前叫做pendingProps, 生成子节点之后会把pendingProps赋值给memoizedProps用于下一次比较.pendingProps和memoizedProps比较可以得出属性是否变动.
fiber.updateQueue: 存储update更新对象的队列, 每一次发起更新, 都需要在该队列上创建一个update对象.
fiber.memoizedState: 上一次生成子节点之后保持在内存中的局部状态.
fiber.dependencies: 该 fiber 节点所依赖的(contexts, events)等, 在context机制章节详细说明.
fiber.mode: 二进制位 Bitfield,继承至父节点,影响本 fiber 节点及其子树中所有节点. 与 react 应用的运行模式有关(有 ConcurrentMode, BlockingMode, NoMode 等选项).
fiber.flags: 标志位, 副作用标记(在 16.x 版本中叫做effectTag, 相应pr), 在ReactFiberFlags.js中定义了所有的标志位. reconciler阶段会将所有拥有flags标记的节点添加到副作用链表中, 等待 commit 阶段的处理.
fiber.subtreeFlags: 替代 16.x 版本中的 firstEffect, nextEffect. 默认未开启, 当设置了enableNewReconciler=true 才会启用, 本系列只跟踪稳定版的代码, 未来版本不会深入解读, 使用示例见源码.
fiber.deletions: 存储将要被删除的子节点. 默认未开启, 当设置了enableNewReconciler=true 才会启用, 本系列只跟踪稳定版的代码, 未来版本不会深入解读, 使用示例见源码.
fiber.nextEffect: 单向链表, 指向下一个有副作用的 fiber 节点.
fiber.firstEffect: 指向副作用链表中的第一个 fiber 节点.
fiber.lastEffect: 指向副作用链表中的最后一个 fiber 节点.
fiber.lanes: 本 fiber 节点所属的优先级, 创建 fiber 的时候设置.
fiber.childLanes: 子节点所属的优先级.
fiber.alternate: 指向内存中的另一个 fiber, 每个被更新过 fiber 节点在内存中都是成对出现(current 和 workInProgress)
// 性能统计相关(开启enableProfilerTimer后才会统计)
// react-dev-tool会根据这些时间统计来评估性能
fiber.actualDuration?: number, // 本次更新过程, 本节点以及子树所消耗的总时间
fiber.actualStartTime?: number, // 标记本fiber节点开始构建的时间
fiber.selfBaseDuration?: number, // 用于最近一次生成本fiber节点所消耗的实现
fiber.treeBaseDuration?: number, // 生成子树所消耗的时间的总和
a1.render = () => [b1, b2, b3];
b1.render = () => [];
b2.render = () => [c1];
b3.render = () => [c2];
c1.render = () => [d1, d2];
c2.render = () => [];
d1.render = () => [];
d2.render = () => [];
walk(a1);
function walk(instance) {
doWork(instance);
const children = instance.render();
children.forEach(walk);
}
function doWork(o) {
console.log(o.name);
}
=》a1, b1, b2, c1, d1, d2, b3, c2
fiber节点创建
class Node {
constructor(instance) {
this.instance = instance;
this.child = null;
this.sibling = null;
this.return = null;
}
}
fiber节点连接
function link(parent, elements) {
//elements为子节点集合
if (elements === null) elements = [];
//reduceRight为反方向开始的reduce
//从最后一个节点开始,进行fiber链表结构的处理
parent.child = elements.reduceRight((previous, current) => {
const node = new Node(current);
node.return = parent;
node.sibling = previous;
return node;
}, null);
return parent.child;
}
const children = [{name: 'b1'}, {name: 'b2'}];
const parent = new Node({name: 'a1'});
const child = link(parent, children);
// the following two statements are true
console.log(child.instance.name === 'b1');
console.log(child.sibling.instance === children[1]);
reconciliation阶段
function App() {
return (
<>
<h1>
<p></p> xiaochen
</h1>
</>
)
}
ReactDOM.render(<App />, document.getElementById("root"));
对于上述节点fiber的遍历顺序
workloop
let root = document.getElementById('root');
// 最开始的工作单元,也就是一个fiber树
let workUnit = {
stateNode:root, // 此fiber对应的静态节点
props:{ // fiber的属性
children:[VnodeEelemet],//虚拟dom,首次渲染时为ReactDom.render的第一个参数的虚拟dom
id:'root'
},
sibling:null,
child:null,
parent:null,
alternate:root.__rootFiberContainer //备份的节点,初始时为null
}
let nextUnitWork = workUnit;
function workLoop(isYieldy) {
//不需要切片
if (!isYieldy) {
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
//shouldYield返回基于deadlineDidExpire和deadline变量的结果,
while (nextUnitOfWork !== null && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
//该帧剩余时间处理完后,开始提交
if(!nextUnitWork){
commitRoot()
}
...
}
}
...
requestIdleCallback(workLoop)
performUnitOfWork和beginWork
function performUnitOfWork(workInProgress) {
//处理完后返回子fiber
let next = beginWork(workInProgress);
//当没有子fiber时,说明已经到了末尾,去completeUnitOfWork进行节点完成工作
//并根据是否有兄弟fiber进行退到父级或返回兄弟fiber
if (next === null) {
next = completeUnitOfWork(workInProgress);
}
return next;
}
function beginWork(workingFiber) {
//在创建fiber的时候,会查看alternate是否存在,具体查看上面的createWorkInProgress
//如果存在说明是更新节点,会根据alternate指向的fiber树根据新生成的虚拟dom(element)diff算法进行复用
/*
if (element.type === alternate.type) {
// 类型相同
newFiber.stateNode = alternate.stateNode;
} else {
// 类型不同,生成新fiber并根据新fiber的tag进行生成stateNode
newFiber.stateNode = createStateNode(newFiber);
}
*/
//这里只考虑首次渲染情况
//创建真实节点挂载到stateNode上
if(!workingFiber.stateNode){
workingFiber.stateNode = document.createElement(workingFiber.type)
Object.keys( workingFiber.props ).forEach( key=>{
if(key !== 'children'){
workingFiber.stateNode[key] = workingFiber.props[key]
}
})
}
//创建子fiber
let previousFiber;
if(!workingFiber.props.children){
return
}
//创建子fiber过程
workingFiber.props.children.forEach( (child,index)=>{
let childFiber = {
type:child.type,
props:child.props,
parent:workingFiber,
tag: 'host_component', //host_component表示为标签节点
stateNode: null,
effectTag:'PLACEMENT', //新增操作
nextEffect:null//下一个有副作用的节点
}
console.log(child,'xxx')
if(index === 0){
workingFiber.child = childFiber
previousFiber = childFiber
//console.log(previousFiber,'previousFiber')
}else{
//console.log(previousFiber,'previousFiberxxx')
previousFiber.sibling = childFiber
previousFiber = childFiber
}
})
return workingFiber.child
}
//获取节点tag
const getTag = vdom => {
if (typeof vdom.type === 'string') {
return 'host_component'
}else if (Object.getPrototypeOf(vdom.type) === Component) {
// 如果是类组件
return 'class_component'
} else {
// 函数组件
return 'function_component'
}
}
//获取节点stateNode
const createStateNode = fiber => {
// 普通节点
if (fiber.tag === 'host_component') {
return createDOMElement(fiber)
}else{
// 组件
return createReactInstance()
}
}
//获取组件实例
createReactInstance = fiber => {
let instance = null;
if (fiber.tag === 'class_component') {
instance = new fiber.type(fiber.props)
} else {
instance = fiber.type;
}
return instance;
}
completeUnitOfWork和completeWork
function completeUnitOfWork(workInProgress) {
while (true) {
let returnFiber = workInProgress.return;
let siblingFiber = workInProgress.sibling;
//进行节点的完成工作(当没有子child、返回到的父节点)
nextUnitOfWork = completeWork(workInProgress);
//当有兄弟节点时,根据兄弟节点进行处理
if (siblingFiber !== null) {
return siblingFiber;
} else if (returnFiber !== null) {
//当没有兄弟节点和子节点时,说明要回退到父节点,进行父节点的完成工作,父节点是已经遍历过的节点所以已经执行了beginWork
workInProgress = returnFiber;
continue;
} else {
//没有兄弟节点和父节点,说明到了根节点
return null;
}
}
}
// 在建立副作用链上,firstEffect作用就是将链头提升,lastEffect是用来构建整个链表的(构建方式是后根遍历)
function completeWork(workingFiber) {
// 构建副作用链nextEffect !== null(对dom进行插入,更改属性都属于副作用操作)
const parentFiber = workingFiber.parent;
if( parentFiber ){
// workingFiber具有firstEffect和lastEffect,说明当前workingFiber不是根节点
// 因为根节点开始会往父节点添加这些属性
//parentFiber具有firstEffect和lastEffect说明,parentFiber下的第一个子节点或第一个子节点内的fiber已经处理过
// 当前fiber有副作用的子链表挂载到父亲身上
if(!parentFiber.firstEffect){
// 副作用头节点提升,及往父节点挂workigFiber下的第一个副作用节点
parentFiber.firstEffect = workingFiber.firstEffect
}
//workingFiber有lastEffect说明不是根节点,为向上的父节点等
if(workingFiber.lastEffect){
//parentFiber有lastEffect,说明parentFiber下的第一个节点处理完毕
//将第二个节点的firstEffect指给parentFiber.lastEffect.nextEffect
//因为子节点都会向父节点归并,所以将父节点归并后的第一个指针firstEffect给其父节点的parentFiber.lastEffect.nextEffect即可得到整段副作用
if(parentFiber.lastEffect){
parentFiber.lastEffect.nextEffect = workingFiber.firstEffect
}
//将父节点指向最后一个副作用节点
parentFiber.lastEffect = workingFiber.lastEffect
}
// 再把自己挂上去(如果自己有副作用的话)
if(workingFiber.effectTag){
//和上一个函数中的区别是,上一个函数中是直接指向workingFiber下的第一个节点,没管workingFiber本身是个副作用节点
//当workingFiber本身有副作用时,更新父节点lastEffect.nextEffect指向
if(parentFiber.lastEffect){
parentFiber.lastEffect.nextEffect = workingFiber
}else{
//只会在第一个节点被处理时进入,因为之后的节点parentFiber.lastEffect都有值
parentFiber.firstEffect = workingFiber
}
parentFiber.lastEffect = workingFiber
}
}
commit提交阶段
function commitRoot(root, finishedWork) {
commitBeforeMutationLifecycles()
commitAllHostEffects();
root.current = finishedWork;
commitAllLifeCycles();
}
commitBeforeMutationLifecycles
function commitBeforeMutationLifecycles() {
while (nextEffect !== null) {
const effectTag = nextEffect.effectTag;
if (effectTag & Snapshot) {
const current = nextEffect.alternate;
commitBeforeMutationLifeCycles(current, nextEffect);
}
nextEffect = nextEffect.nextEffect;
}
}
commitAllHostEffects
function commitAllHostEffects() {
let currentFiber = workUnit.firstEffect;
while(currentFiber){
switch (currentFiber.effectTag) {
//此处伪代码,nextEffect即为当前有副作用的fiber
case Placement: {
commitPlacement(nextEffect);
//如果是类组件,往上查找第一个为普通dom元素的节点
while (currentFiber.parent.tag === 'class_component'||currentFiber.parent.tag==='function_component') {
currentFiber.parent = currentFiber.parent.parent;
}
if (currentFiber.tag === 'host_component') {
currentFiber.parent.stateNode.appendChild(currentFiber.stateNode)
}
...
}
case PlacementAndUpdate: {
commitPlacement(nextEffect);
commitWork(current, nextEffect);
...
}
case Update: {
commitWork(current, nextEffect);
...
}
case Deletion: {
commitDeletion(nextEffect);
...
}
}
currentFiber = currentFiber.nextEffect
}
//完成更新操作后,备份节点到根节点的dom上
workUnit.stateNode.__rootFiberContainer
workUnit = null
}
commitAllLifecycles