关于前端:深入理解React的协调算法

44次阅读

共计 15219 个字符,预计需要花费 39 分钟才能阅读完成。

前言

React 是一个用于构建界面的 JavaScript 库。它的外围是跟踪组件状态变动并将更新后的状态更新到屏幕上。在 React 中,咱们把这个过程称为 reconciliation (协调)。通过调用 setState 办法,React 查看状态或属性是否已更改,并在 UI 层上更新。

背景介绍

首先看一个简略的例子:

class ClickCounter extends React.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>
        ]
    }
}

这是一个简略的计数器的例子, 点击按钮,组件的状态就会在处理程序中更新,组件的状态的更新反过来会引起 span 元素的内容更新。

上面是在协调阶段,React 外部会有各种流动。以下是计数器在协调阶段所做的操作:

  1. 更新 state 的 count 属性
  2. 查看并比拟 ClickCounter 的子代以及 props
  3. 更新 span 元素

协调期间还会执行其余流动,例如调用生命周期办法,更新 ref。这些流动在 Fiber 架构中统称为 ”work”(工作)。work 的类型取决于 React 元素的类型。React 元素有多种类型,比方类组件,函数组件,Portals,DOM 节点等。而 React 元素类型则是由 React.createElement 的第一个参数决定。React.createElement函数在 render 创立元素中调用。

React.createElement 到 Fiber

JSX 编译

<button key="1" onClick={this.onClick}>Update counter</button>
<span key="2">{this.state.count}</span>

JSX 在通过编译后,会失去如下的后果。这是 render 办法真正返回的后果

class ClickCounter {
    ...
    render() {
        return [
            React.createElement(
                'button',
                {
                    key: '1',
                    onClick: this.onClick
                },
                'Update counter'
            ),
            React.createElement(
                'span',
                {key: '2'},
                this.state.count
            )
        ]
    }
}

React.createElement 函数会返回如下的后果:

[
    {$$typeof: Symbol(react.element),
        type: 'button',
        key: "1",
        props: {
            children: 'Update counter',
            onClick: () => { ...}
        }
    },
    {$$typeof: Symbol(react.element),
        type: 'span',
        key: "2",
        props: {children: 0}
    }
]
  • $$typeof 属性,用于将它们标示为 react 元素
  • type,key,props,用于形容元素

而对于组件 <ClickCounter> 的元素,它没有 props 和 key:

{$$typeof: Symbol(react.element),
    key: null,
    props: {},
    ref: null,
    type: ClickCounter
}

Fiber 节点

在协调期间,render 办法返回的 React 元素会被合并到 Fiber 节点树之中。每一个 React 元素都有对应的 Fiber 节点。与 React 元素不同,Fiber 不会在每一次渲染的时候从新创立。Fiber 会保留组件的状态和 DOM。

后面探讨过,依据不同的 React 元素类型,会执行不同的流动。例如,对于 Class 组件会调用生命周期办法以及 render 办法。而对于 DOM 节点,它执行 DOM mutation。因而,每个 React 元素都被转换为相应类型的 Fiber 节点。节点形容了须要实现的 ”work”。

能够将 Fiber 节点看作一种数据解构,示意一个 work 单元。,Fiber 架构还提供了一种跟踪、调度、暂停和停止 work 的办法。

React 会在首次将 React 元素转换为 Fiber 节点时,应用 createFiberFromTypeAndProps 函数创立 Fiber 节点。在更新阶段会复用 Fiber 节点,并应用 React 元素上的数据更新 Fiber 节点上的属性。亦或者挪动,删除 Fiber 节点。

React 源码中的 ChildReconciler 函数蕴含了 Fiber 节点中所有的 work

React 会为每一个 React 元素创立一个 Fiber 节点,咱们会失去一个 Fiber 节点树

Fiber 节点树是通过链表的模式存储的,每一个 Fiber 都领有 child(第一个子节点的援用),sibling(第一个兄弟节点的援用)和 return(父节点的援用),来示意层级关怀。更多内容请参考这篇文章 React Fiber 为什么应用链表来设计组件树

current tree 和 progress tree

在第一次渲染实现后,React 会生成一个 Fiber 树。该树映射了应用程序的状态,这颗树被称为 current tree。当应用程序开始更新时,React 会构建一个workInProgress tree, workInProgress tree 映射了将来的状态。

所有的 ”work” 都是在 workInProgress tree 上的 Fiber 节点上进行的。当 React 开始遍历 current tree 时,它会为每一个现有的 Fiber 节点创立一个备份(alternate 字段),alternate 节点形成了 workInProgress tree。当所有更新和相干的 ”work” 实现。workInProgress tree 会刷新到屏幕上。workInProgress tree此时变为了current tree

React 的外围准则之一是 ” 一致性 ”, 它总是一次性更新 DOM, 不会显示局部后果. workInProgress 就是一个用户不可见的 ” 草稿 ”, React 在它下面解决所有组件, 解决实现后将它再刷新到界面上.

在 React 的源码中,有很多从 workInProgress treecurrent tree中获取 Fiber 节点的函数。比方上面这个函数签名

function updateHostComponent(current, workInProgress, renderExpirationTime) {...}

workInProgress tree的 Fiber 节点领有 current tree 对应节点的援用。反之亦然。

副作用

咱们能够将 React 组件视为应用 state 和 props 计算 UI 的函数。其余的流动,比方手动批改 DOM,调用生命周期都应该被视作一种副作用。在 React 的文档中也提及了这一点

你之前可能曾经在 React 组件中执行过数据获取、订阅或者手动批改过 DOM。咱们对立把这些操作称为“副作用”(side-effects),或者简称为“作用”(effects)。因为它们会影响其余组件,并且在渲染期间无奈实现。

大多数 state 和 props 的更新都会导致副作用。利用 effects 是一种 work 类型。因而 Fiber 节点是一种跟踪更新和 effects 的便捷机制,每一个 Fiber 节点都有与之相关联的 effects。它们被编码在effectTag 字段之中

Fiber 中的 effects 定义了解决更新之后须要做的 ”work”。对于 DOM 元素,”work” 蕴含了增加,更新,删除。对于类组件,包含了更新 ref,调用 componentDidMount 和 componentDidUpdate 生命周期办法。还有其余 effects 对应于其余类型的 Fibber。

Effects list

React 解决更新十分快。为了达到更好的性能程度,采纳了一些乏味的技术。其中之一就是将具备 effects 的 Fiber 节点,构建为线性列表,以不便疾速迭代。迭代线性列表要比迭代树快的多,因为不须要迭代没有 side-effects 的节点。

effects list 的目标是是标记出具备 DOM 更新,或其它与之关联的其余 effects 的节点。effects list 是 finishedWork 树的子集。在 workInProgress treecurrent tree中应用 nextEffect 属性链接在一起。

丹·阿布拉莫夫(Dan Abramov)将 Effects list 提供了一个比喻。将 Fiber 设想成一颗圣诞树,用圣诞灯将所有无效的节点连贯在一起。

为了可视化这一点,让咱们设想上面的 Fiber 树,其中高亮显示的节点有一些“work”要做。

例如,咱们的更新导致将 c2 插入到 DOM 中,d2 和 c1 更改属性,b2 触发生命周期办法。Effects list 列表将把它们链接在一起,这样 React 就能够遍历时跳过其余节点。

能够看到具备 effects 的节点如何链接在一起。当遍历节点时,React 应用 firstEffect 指针确定列表的起始地位。上图的 Effects list 能够用下图示意

Fiber 节点树的根

React 利用都有一个或者多个充当容器的 DOM 元素

const domContainer = document.querySelector('#container');
ReactDOM.render(React.createElement(ClickCounter), domContainer);

React 会为容器创立 FiberRoot 对象,能够应用容器的 DOM 援用拜访 Fiber root 对象:

// Fiber root 对象
const fiberRoot = query('#container')._reactRootContainer._internalRoot

Fiber root 是 React 保留对 Fiber 树援用的中央,Fiber 树存储在 Fiber root 对象的 current 属性中

// Fiber 树
const hostRootFiberNode = fiberRoot.current

Fiber 树的第一个节点是一种非凡的类型节点,叫做 HostRoot。它在外部创立,是最顶层组件的父组件。通过 HostRoot 节点的 stateNode 属性能够拜访 FiberRoot 节点.

// Fiber root 对象
const fiberRoot = query('#container')._reactRootContainer._internalRoot
// hostRoot
const hostRootFiberNode = fiberRoot.current
// true
hostRootFiberNode.stateNode === fiberRoot

咱们能够从 HostRoot 来拜访和摸索整个 Fiber 树。或者能够通过组件的实例中取得单个 Fiber 节点

compInstance._reactInternalFiber

Fiber 节点构造

ClickCounter 组件的 Fiber 节点构造:

{
    stateNode: new ClickCounter,
    type: ClickCounter,
    alternate: null,
    key: null,
    updateQueue: null,
    memoizedState: {count: 0},
    pendingProps: {},
    memoizedProps: {},
    tag: 1,
    effectTag: 0,
    nextEffect: null
}

span DOM 元素的 Fiber 节点构造:

{
    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 节点上有很多字段。咱们之前曾经形容了 alternate(备份节点),effectTag(记录与之关联的 effects), nextEffect(连贯具备 effects 的 Fiber 节点使其成为线性节点)

stateNode 属性

保留对 class 组件实例的援用, DOM 节点或其余与 Fiber 节点相关联的 React 元素类实例的援用。一般来说, 咱们能够说这个属性被用于保留与以后 Fiber 相干的本地状态。

type 属性

定义与此 Fiber 节点相关联的函数或者类。对于 class 组件,type 属性指向构造函数。对于 DOM 元素,type 属性指向 HTML 标记。我常常用这个字段来判断这个 Fiber 节点与那个元素相干。

tag 属性

定义 Fiber 节点的类型。在协调期间应用它确定须要做的 ”work”。如之前所述 ”work” 取决于 React 元素的类型。createFiberFromTypeAndProps 函数将 React 元素映射成绝对应的 Fiber 节点类型。

在咱们的例子中。ClickCounter 的 tag 为 1,示意为 ClassComponent。span 的 tag 为 5,标记为 HostComponent。

updateQueue 属性

state 更新和回调,DOM 更新的队列。

memoizedState 属性

用于创立输入 Fiber 的 state。在解决更新的时候,它映射的是以后界面上出现的 state。

memoizedProps 属性

在上一次渲染过程中用来创立输入的 Fiber props。

pendingProps 属性

曾经更新后的 Fiber props。须要用于子组件和 DOM 元素。

key 属性

一组 children 中的惟一标示。帮忙 React 确定那些产生了更改,新增或删除。更具体的解释在这里

总结

残缺的 Fiber 构造, 能够在这里看到, 在下面的阐明省略了很多的字段比方 child,sibling 并 return。这三个字段是形成链表树结构的要害。以及 expirationTime、childExpirationTime 和 mode,这些字段是特定于 Scheduler 的。

通用算法

React 分两个阶段执行 work:render(渲染)和 commit(提交)

在 render(渲染)阶段,React 将更新利用于通过 setState 或 React.render 调度的组件, 并找出须要在 UI 中更新的内容。

如果是初始渲染,React 将为 render 办法返回的每一个元素创立新的 Fiber 节点。在之后的更新中,将从新应用和更新现有的 Fiber 节点。

render 阶段会构建一个带有 side-effects(副作用)的 Fiber 节点树。effects 形容了下一个 commit(提交)阶段须要实现的“work”。在 commit(提交)阶段,React 会应用标记有 effects 的 Fiber 节点并将其利用于实例上。遍历 Effects list 执行 DOM 更新和其余对用户可见的更改。

请切记,render 阶段的工作是能够异步执行的,React 依据可用工夫解决一个或者多个 Fiber 节点。当产生一些更重要的事件时,React 会进行并保留已实现的工作。等重要的事件解决实现后,React 从中断处持续实现工作。然而有时可能会放弃曾经实现的工作,从顶层从新开始。此阶段执行的工作是对用户是不可见的,因而能够实现暂停。然而在 commit(提交)阶段始终是同步的它会产生用户可见的变动, 例如 DOM 的批改. 这就是 React 须要一次性实现它们的起因。

调用生命周期函数应用 React 的“work”之一。在 render 阶段调用这些生命周期办法:

  • UNSAFE_componentWillMount (deprecated)
  • UNSAFE_componentWillReceiveProps (deprecated)
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • UNSAFE_componentWillUpdate (deprecated)
  • render

因为 render 阶段不会产生 DOM 更新之类的副作用,因而 React 能够异步地对组件进行异步解决更新(甚至可能在多个线程中进行)。然而带有 UNSAFE_前缀的生命周期函数经常会被误用,开发者会把副作用增加到这些生命周期函数中。这可能会导致异步渲染呈现问题

在 commit 阶段调用这些生命周期办法,这些生命周期办法在 commit 阶段执行,所以它们可能蕴含副作用并波及 DOM 更新。

  • getSnapshotBeforeUpdate
  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

渲染 (render) 阶段

协调算法应用 renderRoot 函数从最顶层的 HostRoot 节点开始,跳过曾经解决过的节点,直到找到 work 未实现的节点为止。例如, 当在组件树深处调用 setState 办法, React 从顶部开始疾速的跳过所有父级节点间接取得调用 setState 办法的组件。

WorkLoop

所有的 Fiber 节点在 render 阶段都会在 WorkLoop 中被解决。这是循环同步局部的实现:

function workLoop(isYieldy) {if (!isYieldy) {while (nextUnitOfWork !== null) {nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {...}
}

在下面的代码中,nextUnitOfWork 放弃了对 workInProgress tree 中一个有工作要解决的 Fiber 节点的援用。在 React 遍历 Fiber 树时,会应用 nextUnitOfWork 判断是否有未实现 ”work” 的 Fiber 节点。当节点解决实现“work”后,nextUnitOfWork 会指向下一个 Fiber 节点的援用或者为 null。当 nextUnitOfWork 为 null 时,React 会退出 WorkLoop,并筹备进入到 commit 阶段。

有四个次要的办法用于遍历树,并启动或实现工作:

  • performUnitOfWork
  • beginWork
  • completeUnitOfWork
  • completeWork

为了演示如何应用它们。请查看上面遍历 Fiber 树的演示动画。演示动画中应用了这些函数的简化实现。咱们能够通过演示看到,首先解决子节点的“work”,而后解决父节点的“work”。

应用直线连贯代表同级,应用折线连贯的代笔子级

逐渐拆分下 React 遍历 Fiber 树的过程(首先解决子节点的“work”,而后解决父节点的“work”):

  1. beginWork a1
  2. beginWork b1,completeWork b1
  3. beginWork b2
  4. beginWork c1
  5. beginWork d1, completeWork d1
  6. beginWork d2, completeWork d2
  7. completeWork c1
  8. completeWork b2
  9. beginWork b3
  10. beginWork c2,completeWork c2
  11. completeWork b3
  12. completeWork a1

这个是视频的连贯, 从概念上将 ”begin” 看成进入组件,“complete”看成来到组件。

咱们首先看下 beginWork 和 performUnitOfWork 这两个函数:

function performUnitOfWork(workInProgress) {let next = beginWork(workInProgress);
    if (next === null) {next = completeUnitOfWork(workInProgress);
    }
    return next;
}

function beginWork(workInProgress) {console.log('work performed for' + workInProgress.name);
    return workInProgress.child;
}

performUnitOfWork 从 workInProgress tree 中接管一个 Fiber 节点。而后调用 beginWork 开始解决 Fiber 节点的 work。为了演示,这里只是 log 了 Fiber 节点的 name 字段示意 work 曾经实现。函数 beginWork 总是返回指向循环中下一个子节点或 null。

如果有下一个子节点, 它将在 workLoop 函数中调配给 nextUnitOfWork。如果没有子节点,React 就晓得了达到了分支的结尾。就会实现以后 Fiber 节点的 work。React 会执行它兄弟节点的工作,最初回溯到父节点。这是在 completeUnitOfWork 中实现的。

function completeUnitOfWork(workInProgress) {while (true) {
        let returnFiber = workInProgress.return;
        let siblingFiber = workInProgress.sibling;

        nextUnitOfWork = completeWork(workInProgress);

        if (siblingFiber !== null) {
            // 如果有同级,则返回它。以继续执行同级的工作
            return siblingFiber;
        } else if (returnFiber !== null) {
            // 回溯到上一级
            workInProgress = returnFiber;
            continue;
        } else {
            // 曾经到了 root 节点
            return null;
        }
    }
}

function completeWork(workInProgress) {console.log('work completed for' + workInProgress.name);
    return null;
}

当 workInProgress 节点没有子节点时,会进入此函数。在实现以后 Fiber 的工作后,会查看是否有兄弟节点。如果有,返回同级的兄弟节点的指针,调配给 nextUnitOfWork。React 将会从兄弟节点开始工作。只有解决完子节点所有分支之后, 才会回溯到父节点(所有子节点解决实现后,才会回溯到父节点)。

从实现能够看出,completeUnitOfWork 次要用于迭代,次要工作都是 beginWork 和 completeWork 函数中进行的。

残缺的示例

这里是残缺的示例(beginWork,performUnitOfWork,completeUnitOfWork,completeWork 的繁难实现)

// 首先构建链表树
const a1 = {name: 'a1', child: null, sibling: null, return: null};
const b1 = {name: 'b1', child: null, sibling: null, return: null};
const b2 = {name: 'b2', child: null, sibling: null, return: null};
const b3 = {name: 'b3', child: null, sibling: null, return: null};
const c1 = {name: 'c1', child: null, sibling: null, return: null};
const c2 = {name: 'c2', child: null, sibling: null, return: null};
const d1 = {name: 'd1', child: null, sibling: null, return: null};
const d2 = {name: 'd2', child: null, sibling: null, return: null};
a1.child = b1;
b1.sibling = b2;
b2.sibling = b3;
b2.child = c1;
b3.child = c2;
c1.child = d1;
d1.sibling = d2;
b1.return = b2.return = b3.return = a1;
c1.return = b2;
d1.return = d2.return = c1;
c2.return = b3;

// 以后的指针是 a1
let nextUnitOfWork = a1;
workLoop();

// 开始工作循环
function workLoop() {while (nextUnitOfWork !== null) {nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
}

function performUnitOfWork(workInProgress) {let next = beginWork(workInProgress);
    if (next === null) {next = completeUnitOfWork(workInProgress);
    }
    return next;
}

function beginWork(workInProgress) {log('work performed for' + workInProgress.name);
    return workInProgress.child;
}

function completeUnitOfWork(workInProgress) {while (true) {
        let returnFiber = workInProgress.return;
        let siblingFiber = workInProgress.sibling;

        nextUnitOfWork = completeWork(workInProgress);

        if (siblingFiber !== null) {return siblingFiber;} else if (returnFiber !== null) {
            workInProgress = returnFiber;
            continue;
        } else {return null;}
    }
}

function completeWork(workInProgress) {log('work completed for' + workInProgress.name);
    return null;
}

function log(message) {let node = document.createElement('div');
  node.textContent = message;
  document.body.appendChild(node);
}

提交 (commit) 阶段

提交阶段从 completeRoot 开始。这是 React 更新 DOM,调用 getSnapshotBeforeUpdate,componentDidMount,componentDidUpdate,componentWillUnmount 等生命周期的中央。

React 进入这一阶段时,有两颗树(workInProgress tree 和 current tree)以及 effects list。current tree示意了以后屏幕上出现的状态。render 阶段遍历 current tree 时会生成另一颗树,在源码中被称为 finishWork 或 workInProgress,示意将来须要在屏幕上出现的状态。workInProgress treecurrent tree 构造相似。

调试时,如何获取 current tree 以及workInProgress tree?

// current tree
// 从容器对象上获取 FiberRoot 对象
const fiberRoot = query('#container')._reactRootContainer._internalRoot
// 获取 current tree
const currentTree = fiberRoot.current
// 获取 workInProgress tree
const workInProgressTree = fiberRoot.current.alternate

提交 (commit) 阶段,次要执行 commitRoot 函数,执行以下的操作:

  1. 在有 Snapshot 标记的 Fiber 节点上调用 getSnapshotBeforeUpdate 生命周期办法。
  2. 在有 Deletion 标记的 Fiber 节点上调用 componentWillUnmount 生命周期办法。
  3. 执行所有的 DOM 插入, 更新, 删除。
  4. workInProgress tree 设置为current tree
  5. 在有 Placement 标记的 Fiber 节点上调用 componentDidMount 生命周期办法。
  6. 在有 Update 标记的 Fiber 节点上调用 componentDidUpdate 生命周期办法。

在调用 getSnapshotBeforeUpdate 办法后,React 将 commit,Fiber 树中所有的副作用。分为两步:

第一步,执行所有的 DOM 插入, 更新, 删除和 ref 卸载。而后将 workInProgress tree 设置为 current tree 树。这是在第一步实现之后,第二步之前实现的。因而在 componentWillUnmount 生命周期办法在执行期间,状态仍然是更新之前的。而 componentDidMount/componentDidUpdate 执行时的状态是更新之后的。第二步,执行其余生命周期办法和 ref 回调,这些办法作为独自的过程被执行。

commitRoot 办法的预览:

function commitRoot(root, finishedWork) {
    // 用来执行 getSnapshotBeforeUpdate
    commitBeforeMutationLifecycles()
    // 用户更新 DOM,以及执行 componentWillUnmount
    commitAllHostEffects();
    root.current = finishedWork;
    // 调用 componentDidUpdate 和 componentDidMount 生命周期的中央
    commitAllLifeCycles();}

这些子函数,外部都蕴含了一个循环。循环遍历 effects list,并查看 effects 的类型。当发现类型和子函数的目标雷同时,就利用它。

Pre-mutation lifecycle methods

遍历 effects list,并查看节点是否具备 Snapshot effect 的源代码:

function commitBeforeMutationLifecycles() {while (nextEffect !== null) {
        const effectTag = nextEffect.effectTag;
        if (effectTag & Snapshot) {
            const current = nextEffect.alternate;
            commitBeforeMutationLifeCycles(current, nextEffect);
        }
        nextEffect = nextEffect.nextEffect;
    }
}

如果是 class 组件,调用 getSnapshotBeforeUpdate 生命周期办法。

DOM updates

commitAllHostEffects 是 React 执行 DOM 更新的中央。React 会把 componentWillUnmount 作为 commitDeletion 删除过程中的一部分。

function commitAllHostEffects() {switch (primaryEffectTag) {
        case Placement: {commitPlacement(nextEffect);
            ...
        }
        case PlacementAndUpdate: {commitPlacement(nextEffect);
            commitWork(current, nextEffect);
            ...
        }
        case Update: {commitWork(current, nextEffect);
            ...
        }
        case Deletion: {commitDeletion(nextEffect);
            ...
        }
    }
}

Post-mutation lifecycle methods

commitAllLifecycles 是 React 调用所有残余生命周期办法 componentDidUpdate 和 componentDidMount 的中央。

总结

React 源码很简单,Max Koretskyi 的这篇文章内容也很多,所有总结下这篇博客的要点:

  1. React 的外围是跟踪组件状态变动并将更新后的状态更新到屏幕上。在 React 中,咱们把这个过程称为 reconciliation (协调)。
  2. 协调期间的各种流动在 Fiber 架构中统称为 work(工作),work 的类型取决于 React 元素的类型,React 元素的类型取决于 React.createElement 函数的第一个参数类型。
  3. 协调期间 React 元素会被合并到 Fiber 节点树之中,每一种 React 元素都会一种对应的 Fiber 节点。Fiber 节点不会反复创立,会在第一次渲染后重用。
  4. React 会为每一个 React 元素创立一个 Fiber 节点,咱们会失去一个 Fiber 节点树。Fiber 节点树是通过链表的模式存储的。每一个 Fiber 节点都领有 child,sibling 和 return 字段,用于中断复原遍历。
  5. 在首次渲染时会生成一棵 Fiber 树。被称为current tree。当开始更新时,React 会构建一个workInProgress tree
  6. current tree代表了以后的状态,workInProgress tree代表了将来的状态。
  7. 在 commit 阶段 workInProgress tree 会被设置为current tree
  8. React 在遍历 current tree 时,会为每一个 Fiber 节点创立一个 alternate 字段,alternate 字段保留了 Fiber 节点的备份,alternate 字段上保留的备份 Fiber 节点形成了workInProgress tree
  9. 数据获取、订阅或者手动批改过 DOM 都统称为副作用(side-effects)或者简称为“作用”(effects)。因为它们会影响其余组件,并且在渲染期间无奈实现。
  10. effects 是一种 work 类型。因而 Fiber 节点是一种跟踪更新和 effects 的便捷机制,每一个 Fiber 节点都有与之相关联的 effects。它们被编码在effectTag 字段之中。Fiber 中的 effects 定义了解决更新之后须要做的 ”work”。对于 DOM 元素,”work” 蕴含了增加,更新,删除。对于类组件,包含了更新 ref,调用 componentDidMount 和 componentDidUpdate 生命周期办法。还有其余 effects 对应于其余类型的 Fiber。
  11. React 会将具备 effects 的 Fiber 节点,构建为线性列表,以不便疾速迭代。firstEffect 是列表的开始地位,应用 nextEffect 属性将节点链接在一起(构建成线性列表)。
  12. 能够通过容器 DOM 元素,获取 FiberRoot 对象query('#container')._reactRootContainer._internalRoot
  13. FiberRoot 对象的 current 属性,是 current tree 的第一个节点,被称为 hostRoot。fiberRoot.current
  14. hostRoot 节点的 stateNode 属性,指向 FiberRoot 对象。
  15. 获取workInProgress tree, fiberRoot.current.alternate
  16. Fiber 节点构造见上文。
  17. React 分两个阶段执行 work:render(渲染)和 commit(提交)
  18. render 阶段的工作是能够异步执行的,React 依据可用工夫解决一个或者多个 Fiber 节点。当产生一些更重要的事件时,React 会进行并保留已实现的工作。等重要的事件解决实现后,React 从中断处持续实现工作。然而有时可能会放弃曾经实现的工作,从顶层从新开始。此阶段执行的工作是对用户是不可见的,因而能够实现暂停。
  19. commit(提交)阶段始终是同步的它会产生用户可见的变动, 例如 DOM 的批改. 这就是 React 须要一次性实现它们的起因。
  20. 所有的 Fiber 节点在 render 阶段都会在 WorkLoop 中被解决。WorkLoop 中次要有四个函数,performUnitOfWork,beginWork,completeUnitOfWork,completeWork。WorkLoop 首先解决子节点的“work”,所有子节点解决实现后,回溯而后解决父节点的“work”。从概念上将 ”begin” 看成进入组件,“complete”看成来到组件。
  21. 提交 (commit) 阶段分为两步实现。第一步,执行所有的 DOM 插入, 更新, 删除和 ref 卸载以及执行 componentWillUnmount 生命周期办法。第二步,执行其余生命周期办法和 ref 回调,这些办法作为独自的过程被执行。第一步和第二步两头会将 workInProgress tree 设置为 current tree 树。

参考

  • Inside Fiber: in-depth overview of the new reconciliation algorithm in React

正文完
 0