前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Fiber】:[译]深入解析React的新协调算法

【Fiber】:[译]深入解析React的新协调算法

作者头像
WEBJ2EE
发布2021-02-26 16:08:21
5950
发布2021-02-26 16:08:21
举报
文章被收录于专栏:WebJ2EE
代码语言:javascript
复制
目录
1. 导读
2. 设定一个场景
3. 从React元素到Fiber节点
  3.1. React元素(Elements)
  3.2. Fiber节点(Nodes)
  3.3. current 树、work-in-progress 树
  3.4. 副作用(Side-effects)
  3.5. 作用列表(Effects list)
  3.6. Fiber树的根节点(Root of the fiber tree)
  3.7. Fiber节点结构
    3.7.1 stateNode
    3.7.2 type
    3.7.3 tag
    3.7.4 updateQueue
    3.7.5 memoizedState
    3.7.6 memoizedProps
    3.7.7 pendingProps
    3.7.8 key
4. 整体算法
  4.1. Render阶段
    4.1.1 Main steps of the work loop
  4.2. Commit 阶段
    4.2.1 Pre-mutation lifecycle methods
    4.2.2 DOM updates
    4.2.3 Post-mutation lifecycle methods
5. 总结

深入研究 React 的新架构 Fiber,了解新协调算法的两个主要阶段。我们将详细了解 React 如何更新组件状态(state)、属性(props)以及如何处理子元素(children)。

1. 导读

React使用一个构建用户界面的JavaScript库,它的核心机制是跟踪组件状态的的变化,然后将更新后的状态投射在屏幕上。在React中,我们把这个过程称为协调。我们调用setState方法后,框架会检测state和prop是否发生变化,并重新渲染UI组件。

React文档关于这个机制提供了很好的高层面概述: React元素的角色,生命周期方法,render方法,以及应用于子组件的diff算法。由render方法返回的不可变的React元素普遍被认为是React的“虚拟DOM”。那个术语早期帮助React解释给人们,但它也造成了一些困惑,也不再在React文档中使用,这篇文章中,我会继续称之为React元素的树。

除了React元素的树,框架总还有用于保留状态的内部实例(组件,DOM节点等)的一棵树。从16版本开始,React推出了内部实例树的新实现,以及管理它的算法(代码上称为Fiber)。想要得知Fiber架构带来的好处,可以参见 The how and why on React’s usage of linked list in Fiber(https://indepth.dev/posts/1007/the-how-and-why-on-reacts-usage-of-linked-list-in-fiber-to-walk-the-components-tree)。

这是给你讲解React内部架构系列的第一篇文章。这篇文章中,我想提供算法中重要概念和数据结构的深度概述。一旦我们有足够的背景知识,我们就可以探索这个算法以及用于遍历和操作fiber树的主要方法。系列中的下一篇文章将示范React如何使用这个算法来初始render以及操作state和props的更新,从那里我们将了解到调度(scheduler)的细节、子协调(child reconciliation)操作以及构建更新链表(effect list)。

这里我将给你讲述相当高级的内容,我保证你阅读后可以理解到并发(Concurrent)React内部工作背后的神奇。如果你想成为React的贡献者的话,这个系列的文章也可以作为你的向导。我一个逆向代码的虔诚者(就是喜欢死磕源码),所以这里会有很多关于React@16.6.0的源码链接。

这确实牵扯很多内容,所以如果你没有马上理解也不必有很大压力,一切都值得花时间。需要注意的是你不必了解这些来使用React,这篇文章是关于React如何内部工作的。

2. 设定一个场景

这里有个我们在整个系列中都会使用到的简单应用。我们有个button,简单的增加数字,然后渲染到屏幕上。

这是实现:

代码语言:javascript
复制
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>
        ]
    }
}

正如你看到的,它是一个简单组件,通过 render 方法返回 button 和 span 两个子组件。只要你点击 button,组件的状态就会在处理器中更新,这继而导致span元素中的text的更新。

React在协调(reconciliation)期间有执行很多活动,例如,React在第一次 render 时执行的操作,以及在我们这个简单的应用中状态更新之后:

  • 更新ClickCounter的state中的count参数
  • 获取和比较ClickCounter的子组件以及他们的props
  • 更新span元素的props

在协调期间还执行其他活动,像声明周期方法或者更新refs。所有这些活动在Fiber架构中统一起来被定义为一个“工作(work)”。工作的类型通常取决于React元素(element)的类型,例如,对于一个类组件(class component),React需要创建实例,而对于方法组件(function component)则不需要这样。正如你所知,React中有很多种元素,如类组件、方法组件、host组件(DOM节点)以及 portals 等。元素的类型被定义在createElement方法中的第一个参数,这个方法通常用在render方法中来创建一个元素。

在我们探索这些执行的活动以及主要的Fiber算法时,我们先来对React内部使用的数据结构有个认识。

3. 从React元素到Fiber节点

React中每个组件是一个UI表示,我们可以叫它视图(view)或者模板(template),它由 render 方法返回。这里便是我们 ClickCounter 的模板:

代码语言:javascript
复制
<button key="1" onClick={this.onClick}>Update counter</button>
<span key="2">{this.state.count}</span>

3.1. React元素(Elements)

一旦模板经过JSX编译,最终获得一串React元素。这就是React组件的render方法真实返回的东西,而不是HTML。因为我们没有要求使用JSX,所以ClickCounter组件的render方法也可以写成:

代码语言:javascript
复制
class ClickCounter {
    ...
    render() {
        return [
            React.createElement(
                'button',
                {
                    key: '1',
                    onClick: this.onClick
                },
                'Update counter'
            ),
            React.createElement(
                'span',
                {
                    key: '2'
                },
                this.state.count
            )
        ]
    }
}

在用 render 方法中调用 React.createElement 方法将创建两个数据结构:

代码语言:javascript
复制
[
    {
        $$typeof: Symbol(react.element),
        type: 'button',
        key: "1",
        props: {
            children: 'Update counter',
            onClick: () => { ... }
        }
    },
    {
        $$typeof: Symbol(react.element),
        type: 'span',
        key: "2",
        props: {
            children: 0
        }
    }
]

你可以看到 React 在这些对象上添加 $$typeof 当作 React 元素的来唯一标识,并且我们还有 type、key 和 props 等属性来描述这个元素,这些值就是从你传递给 React.createElement 方法的参数中得到的。这里注意,React 是如何把文本内容表达成 span 和 button 节点的孩子,click 事件处理器如何成为 button 元素 props 的一部分,以及 React 元素上其他一些字段(比如:ref)已经超出了本文的范畴。

React元素 ClickCounter 没有任何 props 或者 key:

代码语言:javascript
复制
{
    $$typeof: Symbol(react.element),
    key: null,
    props: {},
    ref: null,
    type: ClickCounter
}

3.2. Fiber节点(Nodes)

协调(reconciliation)期间,由render方法返回的每个React元素都将合并到fiber节点的树中,每个React元素都有相对应的fiber节点。跟 React元素不同,fiber节点不会在每次render时重新创建,它们是带有组件状态以及DOM的可变数据结构。

我们之前讨论过,框架会根据React元素类型的不同来执行不同的活动。在我们的示例应用中,对于类组件ClickCounter,它调用生命周期和 render 方法,而宿主(浏览器) span 组件(DOM节点)则执行 DOM 变化,所以每个 React 元素转化成类型相对应的 Fiber 节点,这些类型描述了需要完成的工作。

你可以把 Fiber 想象成一种数据结构,它描述了一组需要完成的工作。Fiber 架构同时也提供了一种便捷的方式跟踪、编排、暂停、放弃工作。

当React元素首次被转化成fiber节点时,React 会调用 createFiberFromTypeAndProps 方法,并使用 React 元素中的数据创建 fiber节点。在后续的更新操作中,React 会重用这个 fiber 节点,并根据对应 React 元素中的数据进行必要的属性更新。React 也可能需要基于key属性在层级中移动节点,或者如果对应的 React元素不再由render方法返回时,则删除掉它。

查看 ChildReconciler方法源码(https://github.com/facebook/react/blob/95a313ec0b957f71798a69d8e83408f40e76765b/packages/react-reconciler/src/ReactChildFiber.js#L239),你可以看到所有 React 在 fiber 节点上执行的活动以及对应方法。

因为 React 为每个 React 元素都创建了一个fiber,所以只要我们有这些元素的一棵树,那我们就会有 fiber 节点的一棵树。在我们示例应用中,它看起来如下:

所有fiber节点通过一个链表链接起来,这个链表使用了fiber节点中属性:child、sibling和return。关于如何和为何这种方式,可以查阅我的文章The how and why on React’s usage of linked list in Fiber(https://medium.com/dailyjs/the-how-and-why-on-reacts-usage-of-linked-list-in-fiber-67f1014d0eb7)。

3.3. current 树、work-in-progress 树

在第一次渲染(render)之后,React 最后会得到了一颗fiber树,它反映了用于渲染UI的应用的状态,这颗树被当作current。当React开始处理更新时,它构建所谓的workInProgress 树来反映未来用于刷新到屏幕上的状态。

所有工作都在来自 workInProgress 树的 fiber 上执行。当React经过当前树时,对于每一个先存在的 fiber 节点,它都会创建一个替代(alternate)节点,这些节点组成了workInProgress 树。这个节点是使用 render 方法返回的React元素的数据创建的。一旦更新处理完以及所有相关工作完成,React就有一颗替代树来准备刷新屏幕。一旦这颗workInProgress树渲染(render)在屏幕上,它便成了当前树。

React的设计原则之一是一致性。React总是一次性更新DOM,而不是只显示部分结果。这颗 workInProgress 树为当做是‘草稿’,它对用户是不可见的,以至于React可以先处理所有组件,然后再刷新他们的改变到屏幕上。

在源码中,可以看到很多方法同时从 current 和 workInProgress 树中获取fiber 节点,举个例子:

代码语言:javascript
复制
function updateHostComponent(current, workInProgress, renderExpirationTime) {...}

每个 fiber 节点中的 alternate 字段持有指向另一棵树对等节点的引用。来自 current 树的节点有着指向 workInProgress 树的引用,来自 workInProgress 树的节点有指向 current 树节点的引用。

3.4. 副作用(Side-effects)

我们可以认为 React 中的组件是使用 state 和 props 计算UI展示的方法。每个其他活动,例如修改DOM、调用生命周期方法,应当看做是一个“副作用(side-effect)”,或者说“作用(effect)”。“作用(Effects)”也在官方文档中提到过。

You’ve likely performed data fetching, subscriptions, or manually changing the DOM from React components before. We call these operations “side effects” (or “effects” for short) because they can affect other components and can’t be done during rendering.

你可以看到很多state和props是如何造成副作用的,既然应用副作用是一个工作的类型,那一个fiber节点就是除了更新之外还用于跟踪作用的简明机制。每一个fiber节点可以有很多关联的作用,它们被编码到effectTag字段中(effectTag使用位运算的妙处啦)。

所以Fiber中的作用(effects)基本上定义了一个组件实例在其更新操作之后需要完成的工作(work),对于宿主组件(DOM元素),这个工作包括更新、添加和删除元素;对于类组件,React可能需要更新refs,以及调用componentDidMount和componentDidUpdate生命周期方法。这里当然还有其他一些与fiber类型相对应的作用。

3.5. 作用列表(Effects list)

React处理更新很快,为了实现这个层次的性能,它采用了个别有趣的技巧,比如,将含有作用的fiber节点用线性列表表示,从而可以快速迭代。迭代线性列表比树要快,且可以不必花时间在没有副作用的节点上。

这个列表的目的是用于标记一些节点,这些节点有 DOM 更新或者与其关联的副作用。这个列表是 finishedWork 的子集,且通过 nextEffect 属性链接起来,而不是 current 和workInProgress树中使用的 child 属性。

Dan Abramov offered an analogy for an effects list. He likes to think of it as a Christmas tree, with “Christmas lights” binding all effectful nodes together. To visualize this, let’s imagine the following tree of fiber nodes where the highlighted nodes have some work to do. For example, our update caused c2 to be inserted into the DOM, d2 and c1 to change attributes, and b2 to fire a lifecycle method. The effect list will link them together so React can skip other nodes later:

You can see how the nodes with effects are linked together. When going over the nodes, React uses the firstEffect pointer to figure out where the list starts. So the diagram above can be represented as a linear list like this:

3.6. Fiber树的根节点(Root of the fiber tree)

每个 React 应用有一个或多个 DOM 元素作为容器,在我们的例子中,它是ID为 container 的 div 元素:

代码语言:javascript
复制
const domContainer = document.querySelector('#container');
ReactDOM.render(React.createElement(ClickCounter), domContainer);

React为这些容器的每个创建一个fiber根节点,你可以通过DOM元素的引用访问它:

代码语言:javascript
复制
const fiberRoot = query('#container')._reactRootContainer._internalRoot

这个 fiber 根节点就是 React 持有fiber树引用的地方,它保存在fiber根节点的current属性上:

代码语言:javascript
复制
const hostRootFiberNode = fiberRoot.current

fiber 树开始于 HostRoot 的 fiber 节点的一个特殊类型,它由内部创建并将顶层组件作为父节点。这里有一个通过从 stateNode 属性从 HostRootfiber 节点返回到FiberRoot 的连接:

代码语言:javascript
复制
fiberRoot.current.stateNode === fiberRoot; // true

‍你可以通过fiber根节点获取HostFiber节点来探索fiber树,或者你可以像这样从组件实例中获取独立的fiber节点:

代码语言:javascript
复制
compInstance._reactInternalFiber

3.7. Fiber节点结构

我们来看一下由 ClickCounter 组件创建的fiber节点的数据结构:

代码语言:javascript
复制
{
    stateNode: new ClickCounter,
    type: ClickCounter,
    alternate: null,
    key: null,
    updateQueue: null,
    memoizedState: {count: 0},
    pendingProps: {},
    memoizedProps: {},
    tag: 1,
    effectTag: 0,
    nextEffect: null
}

以及span DOM元素:

代码语言:javascript
复制
{
    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 和 nextEffect 的目的,现在我看看为何还需要其他字段。

3.7.1 stateNode

Holds the reference to the class instance of a component, a DOM node or other React element type associated with the fiber node. In general, we can say that this property is used to hold the local state associated with a fiber.

3.7.2 type

Defines the function or class associated with this fiber. For class components, it points to the constructor function and for DOM elements it specifies the HTML tag. I use this field quite often to understand what element a fiber node is related to.

3.7.3 tag

Defines the type of the fiber. It’s used in the reconciliation algorithm to determine what work needs to be done. As mentioned earlier, the work varies depending on the type of React element. The function createFiberFromTypeAndProps maps a React element to the corresponding fiber node type. In our application, the property tag for the ClickCounter component is 1 which denotes a ClassComponent and for the span element it’s 5 denoting a HostComponent.

3.7.4 updateQueue

A queue of state updates, callbacks and DOM updates.

3.7.5 memoizedState

State of the fiber that was used to create the output. When processing updates it reflects the state that’s currently rendered on the screen.

3.7.6 memoizedProps

Props of the fiber that were used to create the output during the previous render.

3.7.7 pendingProps

Props that have been updated from new data in React elements and need to be applied to child components or DOM elements.

3.7.8 key

Unique identifier with a group of children to help React figure out which items have changed, have been added or removed from the list. It’s related to the “lists and keys” functionality of React described here.

You can find the complete structure of a fiber node here. I’ve omitted a bunch of fields in the explanation above. Particularly, I skipped the pointers child, sibling and return that make up a tree data structure which I described in my previous article. And a category of fields like expirationTime, childExpirationTime and mode that are specific to Scheduler.

4. 整体算法

React执行工作主要有两个阶段:render 和 commit。

During the first render phase React applies updates to components scheduled through setState or React.render and figures out what needs to be updated in the UI. If it’s the initial render, React creates a new fiber node for each element returned from the render method. In the following updates, fibers for existing React elements are re-used and updated. The result of the phase is a tree of fiber nodes marked with side-effects. The effects describe the work that needs to be done during the following commit phase.During this phase React takes a fiber tree marked with effects and applies them to instances. It goes over the list of effects and performs DOM updates and other changes visible to a user.

It’s important to understand that the work during the first render phase can be performed asynchronously. React can process one or more fiber nodes depending on the available time, then stop to stash the work done and yield to some event. It then continues from where it left off. Sometimes though, it may need to discard the work done and start from the top again.These pauses are made possible by the fact that the work performed during this phase doesn’t lead to any user-visible changes, like DOM updates. In contrast, the following commit phase is always synchronous. This is because the work performed during this stage leads to changes visible to the user, e.g. DOM updates. That’s why React needs to do them in a single pass.

Calling lifecycle methods is one type of work performed by React. Some methods are called during the render phase and others during the commit phase. Here’s the list of lifecycles called when working through the first render phase:

  • [UNSAFE_]componentWillMount (deprecated)
  • [UNSAFE_]componentWillReceiveProps (deprecated)
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • [UNSAFE_]componentWillUpdate (deprecated)
  • render

As you can see, some legacy lifecycle methods that are executed during the render phase are marked as UNSAFE from the version 16.3. They are now called legacy lifecycles in the docs. They will be deprecated in future 16.x releases and their counterparts without the UNSAFE prefix will be removed in 17.0. You can read more about these changes and the suggested migration path here.

Are you curious about the reason for this?

Well, we’ve just learned that because the render phase doesn’t produce side-effects like DOM updates, React can process updates asynchronously to components asynchronously (potentially even doing it in multiple threads).However, the lifecycles marked with UNSAFE have often been misunderstood and subtly misused. Developers tended to put the code with side-effects inside these methods which may cause problems with the new async rendering approach. Although only their counterparts without the UNSAFE prefix will be removed, they are still likely to cause issues in the upcoming Concurrent Mode (which you can opt out of).

Here’s the list of lifecycle methods executed during the second commit phase:

  • getSnapshotBeforeUpdate
  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

Because these methods execute in the synchronous commit phase, they may contain side effects and touch the DOM.

Okay, so now we have the background to take a look at generalized algorithm used to walk the tree and perform work. Let’s dive in.

4.1. Render阶段

The reconciliation algorithm always starts from the topmost HostRoot fiber node using the renderRoot function. However, React bails out of (skips) already processed fiber nodes until it finds the node with unfinished work.For example, if you call setState deep in the components tree, React will start from the top but quickly skip over the parents until it gets to the component that had its setState method called.

4.1.1 Main steps of the work loop

All fiber nodes are processed in the work loop. Here is the implementation of the synchronous part of the loop:

代码语言:javascript
复制
function workLoop(isYieldy) {
  if (!isYieldy) {
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {...}
}

In the code above, the nextUnitOfWork holds a reference to the fiber node from the workInProgress tree that has some work to do. As React traverses the tree of Fibers, it uses this variable to know if there’s any other fiber node with unfinished work. After the current fiber is processed, the variable will either contain the reference to the next fiber node in a tree or null. In that case React exits the work loop and is ready to commit the changes.

There are 4 main functions that are used to traverse the tree and initiate or complete the work:

  • performUnitOfWork
  • beginWork
  • completeUnitOfWork
  • completeWork

To demonstrate how they are used, take a look at the following animation of traversing a fiber tree. I’ve used the simplified implementation of these functions for the demo. Each function takes a fiber node to process and as React goes down the tree you can see the currently active fiber node changes. You can clearly see on the video how the algorithm goes from one branch to the other. It first completes the work for children before moving to parents.

Note that straight vertical connections denote siblings, whereas bent connections denote children, e.g. b1 doesn’t have children, while b2 has one child c1.

Here’s the link to the video where you can pause the playback and inspect the current node and the state of functions. Conceptually, you can think of “begin” as “stepping into” a component, and “complete” as “stepping out” of it. You can also play with the example and the implementation here as I explain what these functions do.

Let’s start with the first two functions performUnitOfWork and beginWork:

代码语言:javascript
复制
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;
}

The function performUnitOfWork receives a fiber node from the workInProgress tree and starts the work by calling beginWork function. This is the function that will start all the activities that need to be performed for a fiber. For the purposes of this demonstration, we simply log the name of the fiber to denote that the work has been done. The function beginWork always returns a pointer to the next child to process in the loop or null.

If there’s a next child, it will be assigned to the variable nextUnitOfWork in the workLoop function. However, if there’s no child, React knows that it reached the end of the branch and so it can complete the current node. Once the node is completed, it’ll need to perform work for siblings and backtrack to the parent after that. This is done in the completeUnitOfWork function:

代码语言:javascript
复制
function completeUnitOfWork(workInProgress) {
    while (true) {
        let returnFiber = workInProgress.return;
        let siblingFiber = workInProgress.sibling;

        nextUnitOfWork = completeWork(workInProgress);

        if (siblingFiber !== null) {
            // If there is a sibling, return it
            // to perform work for this sibling
            return siblingFiber;
        } else if (returnFiber !== null) {
            // If there's no more work in this returnFiber,
            // continue the loop to complete the parent.
            workInProgress = returnFiber;
            continue;
        } else {
            // We've reached the root.
            return null;
        }
    }
}

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

You can see that the gist of the function is a big while loop. React gets into this function when a workInProgress node has no children. After completing the work for the current fiber, it checks if there’s a sibling. If found, React exits the function and returns the pointer to the sibling. It will be assigned to the nextUnitOfWork variable and React will perform the work for the branch starting with this sibling. It’s important to understand that at this point React has only completed work for the preceding siblings. It hasn’t completed work for the parent node. Only once all branches starting with child nodes are completed does it complete the work for the parent node and backtracks.

As you can see from the implementation, both and completeUnitOfWork are used mostly for iteration purposes, whereas the main activities take place in the beginWork and completeWork functions. In the following articles in the series we’ll learn what happens for the ClickCounter component and the span node as React steps into beginWork and completeWork functions.

4.2. Commit 阶段

The phase begins with the function completeRoot. This is where React updates the DOM and calls pre and post mutation lifecycle methods.

When React gets to this phase, it has 2 trees and the effects list. The first tree represents the state currently rendered on the screen. Then there’s an alternate tree built during the render phase. It’s called finishedWork or workInProgress in the sources and represents the state that needs to be reflected on the screen. This alternate tree is linked similarly to the current tree through the child and sibling pointers.

And then, there’s an effects list — a subset of nodes from the finishedWork tree linked through the nextEffect pointer. Remember that the effect list is the result of running the render phase. The whole point of rendering was to determine which nodes need to be inserted, updated, or deleted, and which components need to have their lifecycle methods called. And that’s what the effect list tells us. And it’s exactly the set of nodes that’s iterated during the commit phase.

For debugging purposes, the current tree can be accessed through the current property of the fiber root. The finishedWork tree can be accessed through the alternate property of the HostFiber node in the current tree.

The main function that runs during the commit phase is commitRoot. Basically, it does the following:

  • Calls the getSnapshotBeforeUpdate lifecycle method on nodes tagged with the Snapshot effect
  • Calls the componentWillUnmount lifecycle method on nodes tagged with the Deletion effect
  • Performs all the DOM insertions, updates and deletions
  • Sets the finishedWork tree as current
  • Calls componentDidMount lifecycle method on nodes tagged with the Placement effect
  • Calls componentDidUpdate lifecycle method on nodes tagged with the Update effect

After calling the pre-mutation method getSnapshotBeforeUpdate, React commits all the side-effects within a tree. It does it in two passes. The first pass performs all DOM (host) insertions, updates, deletions and ref unmounts. Then React assigns the finishedWork tree to the FiberRoot marking the workInProgress tree as the current tree. This is done after the first pass of the commit phase, so that the previous tree is still current during componentWillUnmount, but before the second pass, so that the finished work is current during componentDidMount/Update. In the second pass React calls all other lifecycle methods and ref callbacks. These methods are executed as a separate pass so that all placements, updates, and deletions in the entire tree have already been invoked.

Here’s the gist of the function that runs the steps described above:

代码语言:javascript
复制
function commitRoot(root, finishedWork) {
    commitBeforeMutationLifecycles()
    commitAllHostEffects();
    root.current = finishedWork;
    commitAllLifeCycles();
}

Each of those sub-functions implements a loop that iterates over the list of effects and checks the type of effects. When it finds the effect pertaining to the function’s purpose, it applies it.

4.2.1 Pre-mutation lifecycle methods

Here is, for example, the code that iterates over an effects tree and checks if a node has the Snapshot effect:

代码语言:javascript
复制
function commitBeforeMutationLifecycles() {
    while (nextEffect !== null) {
        const effectTag = nextEffect.effectTag;
        if (effectTag & Snapshot) {
            const current = nextEffect.alternate;
            commitBeforeMutationLifeCycles(current, nextEffect);
        }
        nextEffect = nextEffect.nextEffect;
    }
}

For a class component, this effect means calling the getSnapshotBeforeUpdate lifecycle method.

4.2.2 DOM updates

commitAllHostEffects is the function where React performs DOM updates. The function basically defines the type of operation that needs to be done for a node and executes it:

代码语言:javascript
复制
function commitAllHostEffects() {
    switch (primaryEffectTag) {
        case Placement: {
            commitPlacement(nextEffect);
            ...
        }
        case PlacementAndUpdate: {
            commitPlacement(nextEffect);
            commitWork(current, nextEffect);
            ...
        }
        case Update: {
            commitWork(current, nextEffect);
            ...
        }
        case Deletion: {
            commitDeletion(nextEffect);
            ...
        }
    }
}

It’s interesting that React calls the componentWillUnmount method as part of the deletion process in the commitDeletion function.

4.2.3 Post-mutation lifecycle methods

commitAllLifecycles is the function where React calls all remaining lifecycle methods componentDidUpdate and componentDidMount.

5. 总结

We’re finally done. Let me know what you think about the article or ask questions in the comments. Check out the next article in the series In-depth explanation of state and props update in React. I have many more articles in the works providing in-depth explanation for scheduler, children reconciliation process and how effects list is built. I also have plans to create a video where I’ll show how to debug the application using this article as a basis.

参考:

Inside Fiber: in-depth overview of the new reconciliation algorithm in React: https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 WebJ2EE 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档