美文网首页
【译】了解React源代码-UI更新(DOM树)9

【译】了解React源代码-UI更新(DOM树)9

作者: nextChallenger | 来源:发表于2019-10-16 14:58 被阅读0次

    【译】了解React源代码-初始渲染(简单组件)1
    【译】了解React源代码-初始渲染(简单组件)2
    【译】了解React源代码-初始渲染(简单组件)3
    【译】了解React源代码-初始渲染(类组件)4
    【译】了解React源代码-初始渲染(类组件)5
    【译】了解React源代码-UI更新(事务)6
    【译】了解React源代码-UI更新(事务)7
    【译】了解React源代码-UI更新(单个DOM)8
    【译】了解React源代码-UI更新(DOM树)9


    上一次,我们经历了从setState()到更新单个DOM的过程。 我们还分析了差异算法,因为该算法是为比更新单个DOM节点复杂得多的任务而设计的,因此远远不够完善。

    这次我们将使用两个示例更深入地研究差异算法。 更具体地说,我们看一下该算法如何处理变异的DOM树。

    注意,本文中使用的示例均来自官方文档,该文档还提供了差异算法的高级描述。 如果您对主题不太熟悉,则可能需要先阅读。

    示例1.无密钥节点的差异

    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          data : ['one', 'two'],
        };
    
        this.timer = setInterval(
          () => this.tick(),
          5000
        );
      }
    
      tick() {
        this.setState({
          data: ['new', 'one', 'two'],
        });
      }
    
      render() {
        return (
          <ul>
          {
            this.state.data.map(function(val, i) {
              return <li>{ val }</li>;
            })
          }
          </ul>
        );
      }
    }
    
    export default App;
    

    转义版本的render()

    render() {
      return React.createElement(
        'ul',
        null,
        this.state.data.map(function (val, i) {
          return React.createElement(
            'li',
            null,
            ' ',
            val,
            ' '
          );
        })
      );
    }
    

    新旧虚拟DOM树

    我们知道虚拟DOM树render()方法的结果是{第四篇}(对React.createElement()的嵌套调用)

    We ignore the ReactElement’s corresponding controllers (i.e., ReactDOMComponent) for simplicity.
    为了简单起见,我们忽略了ReactElement的相应控制器(即ReactDOMComponent)。

    上图给出了由初始渲染生成的旧虚拟DOM树。 与{上一篇}中一样,setState()会在5秒钟后触发,从而启动更新过程,

    Figure-I

    考虑到这种数据结构,我们跳过与{上一篇}相同的逻辑过程(大部分在transaction之前),然后直接转到差异算法,

    _updateRenderedComponent: function (transaction, context) {
      var prevComponentInstance = this._renderedComponent; // scr: -> 1)
    
      // scr: ------------------------------------------------------> 2)
      var prevRenderedElement = prevComponentInstance._currentElement;
    
      // scr: create a new DOM tree
      var nextRenderedElement = this._renderValidatedComponent();
    
      var debugID = 0;
    
      // scr: DEV code
    ...
    
      if (shouldUpdateReactComponent( // scr: ----------------------> 3)
          prevRenderedElement,
          nextRenderedElement)
      ) {
        ReactReconciler.receiveComponent( // scr: ------------------> 5)
          prevComponentInstance,
          nextRenderedElement,
          transaction,
          this._processChildContext(context)
        );
      } else { // scr: ---------------------------------------------> 4)
      // scr: code that is not applicable this time
    ...
      }
    },
    
    ReactCompositeComponent@renderers/shared/stack/reconciler/ReactCompositeComponent.js
    

    The steps 1–5) are also identical to {last post}.
    步骤1-5)也与{上一篇}相同。

    ,该算法首先创建新的DOM树({Figure-I}中右边的那个)和ReactCompositeComponent._renderValidatedComponent()。 {第四篇}

    根节点是相同的,因此可以“区分”其直接子节点

    由于ReactElement [1]的类型相同(“ ul”),因此逻辑转到{上一篇}中的5)。

    receiveComponent: function (nextElement, transaction, context) {
      var prevElement = this._currentElement;
      this._currentElement = nextElement;
      this.updateComponent(transaction,
                           prevElement,
                           nextElement,
                           context);
    },
    
    updateComponent: function(
      transaction,
      prevElement,
      nextElement,
      context
    ) {
      var lastProps = prevElement.props;
      var nextProps = this._currentElement.props;
    
    // scr: code that is not applicable this time
    ...
    
    // scr: ------------------------------------------------------> 1)
      this._updateDOMProperties(lastProps, nextProps, transaction);
    
    // scr: ------------------------------------------------------> 2)
      this._updateDOMChildren(lastProps, nextProps, transaction, context);
    
    // scr: code that is not applicable this time
    ...
    },
    
    ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js
    

    在{上一篇}步骤1)中更新DOM节点属性; 和2)更新其内容。

    但是对于根节点(ReactElement [1]),整个ReactDOMComponent.updateComponent()方法调用的唯一目的是递归和更新ReactElement [1]的直接子对象,因为节点的属性及其内容均未更改。

    我还将{上一篇}的静态调用堆栈作为线索进行扩展:

    ...                                                            ___
    ReactReconciler.receiveComponent()      <----------------|      |
      |-ReactDOMComponent.receiveComponent()                 |      |
        |-this.updateComponent()                             |      |
          |-this._updateDOMProperties()                      |      |
            |-CSSPropertyOperations.setValueForStyles()      |      |
          |-this._updateDOMChildren()                        |      |
            |-this.updateTextContent()                       |   diffing
            |-this._updateDOMChildren() (the focus this time)|      |
              |-this.updateChildren()                        |      |
              |=this._updateChildren()                       |      |
                |-this._reconcilerUpdateChildren()           |      |
                  |-this.flattenChildren()                   |      |
                  |-ReactChildReconciler.updateChildren() ---|      |
                                                                   ---
    

    如前所述,递归从ReactDOMComponent._updateDOMChildren()开始。 在以下各节中,我们将遵循层次结构,一次执行一个函数,然后进入堆栈的底部。

    ReactDOMComponent._updateDOMChildren()-开始递归直接子级

    _updateDOMChildren: function (
     lastProps, nextProps, transaction, context
    ) {
     // scr: code for content updating
    ...
     var nextChildren = nextContent != null ? null : nextProps.children;
    
     if (lastChildren != null && nextChildren == null) { // scr: --> 1)
       this.updateChildren(null, transaction, context);
     } else if (lastHasContentOrHtml && !nextHasContentOrHtml) {
       // scr: code for content updating
    ...
     }
    
     if (nextContent != null) {
       if (lastContent !== nextContent) {
         // scr: code for content updating
    ...
       } else if (nextHtml != null) {
         // scr: code for content updating
    ...
       } else if (nextChildren != null) {
         // scr: DEV code
    ...
         // scr: --------------------------------------------------> 2)
         this.updateChildren(nextChildren, transaction, context);
     }
    },
    
    ReactDOMComponent@renderers/dom/shared/ReactDOMComponent.js
    

    I fold up the content updating related code so we can focus on DOM children recursing
    我将内容更新相关的代码折叠起来,以便我们专注于DOM子代递归

    1)仅在必要时删除子级(lastChildren!= null && nextChildren == null);

    2)开始递归。

    ReactMultiChild.updateChildren()I —实际工作的马

    在使用别名方法或具有很少(预处理)操作的方法之后,我们来研究以下工作:I)递归虚拟DOM子代,比较它们的新/旧版本,并相应地修改ReactDOMComponent的名称( 为简单起见我们将其命名为虚拟DOM操作); 和II)将操作提交给实际的DOM。

    the role of this ReactMultiChild.updateChildren() is similar to that of mountComponentIntoNode() in initial rendering {post two}
    ReactMultiChild.updateChildren()的角色在初始呈现中类似于mountComponentIntoNode()的角色{第二篇}

    updateChildren: function (
      nextNestedChildrenElements,
      transaction,
      context
    ) {
      // Hook used by React ART
      this._updateChildren(nextNestedChildrenElements, transaction, context);
    },
    
    _updateChildren: function (
      nextNestedChildrenElements,
      transaction,
      context
    ) {
      var prevChildren = this._renderedChildren;
      var removedNodes = {};
      var mountImages = [];
      var nextChildren = this._reconcilerUpdateChildren( // scr: ---> I)
                           prevChildren, // scr: ------------------>  i)
                           nextNestedChildrenElements, // scr: ----> ii)
                           mountImages,
                           removedNodes,
                           transaction,
                           context
                         );
    
      if (!nextChildren && !prevChildren) {
        return;
      }
    
      // scr: -----------------------------------------------------> II)
      var updates = null;
      var name;
      // `nextIndex` will increment for each child in `nextChildren`, but
      // `lastIndex` will be the last index visited in `prevChildren`.
      var nextIndex = 0;
      var lastIndex = 0;
      
      // `nextMountIndex` will increment for each newly mounted child.
      var nextMountIndex = 0;
      var lastPlacedNode = null;
      for (name in nextChildren) {
        if (!nextChildren.hasOwnProperty(name)) {
          continue;
        }
    
        var prevChild = prevChildren && prevChildren[name];
        var nextChild = nextChildren[name];
        if (prevChild === nextChild) {
          updates = enqueue(updates, this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex));
          lastIndex = Math.max(prevChild._mountIndex, lastIndex);
          prevChild._mountIndex = nextIndex;
        } else {
          if (prevChild) {
            // Update `lastIndex` before `_mountIndex` gets unset by unmounting.
            lastIndex = Math.max(prevChild._mountIndex, lastIndex);
            // The `removedNodes` loop below will actually remove the child.
          }
    
          // The child must be instantiated before it's mounted.
          updates = enqueue(updates, this._mountChildAtIndex(nextChild, mountImages[nextMountIndex], lastPlacedNode, nextIndex, transaction, context));
          nextMountIndex++;
        }
    
        nextIndex++;
        lastPlacedNode = ReactReconciler.getHostNode(nextChild);
      }
    
      // Remove children that are no longer present.
      for (name in removedNodes) {
        if (removedNodes.hasOwnProperty(name)) {
          updates = enqueue(updates, this._unmountChild(prevChildren[name], removedNodes[name]));
        }
      }
    
      if (updates) {
        processQueue(this, updates);
      }
      this._renderedChildren = nextChildren;
    
      // scr: DEV code
    ...
    
    ReactMultiChild@renderers/shared/stack/reconciler/ReactMultiChild.js
    

    我们首先看一下虚拟DOM操作,I)。 请注意,负责方法ReactDOMComponent._reconcilerUpdateChildren()的两个输入参数是:i)prevChildren,即ReactDOMComponent._renderedChildren,在初始渲染中将其设置为其子ReactDOMComponents的对象{第五篇}; ii)nextNestedChildrenElements,即从ReactDOMComponent._updateDOMChildren()传递的nextProps.children

    ReactDOMComponent._reconcilerUpdateChildren()—虚拟DOM操作

    _reconcilerUpdateChildren: function (
      prevChildren,
      nextNestedChildrenElements,
      mountImages,
      removedNodes,
      transaction,
      context
    ) {
      var nextChildren;
      var selfDebugID = 0;
      // scr: DEV code
    ...
    
      nextChildren = flattenChildren(      // scr: -----------------> 1)
                       nextNestedChildrenElements,
                       selfDebugID);
    
      ReactChildReconciler.updateChildren( // scr: -----------------> 2)
                       prevChildren,
                       nextChildren,
                       mountImages,
                       removedNodes,
                       transaction,
                       this,
                       this._hostContainerInfo,
                       context, selfDebugID);
    
      return nextChildren;
    },
    
    ReactMultiChild@renderers/shared/stack/reconciler/ReactMultiChild.js
    

    在2)可以遍历和比较虚拟DOM之前,此方法1)调用

    flattenChildren()—将ReactElement数组转换为对象(map)

    function flattenChildren(children, selfDebugID) {
      if (children == null) {
        return children;
      }
      var result = {};
    
      // scr: DEV code
    ...
    
      {
        traverseAllChildren(children, flattenSingleChildIntoContext, result);
      }
    
      return result;
    }
    
    flattenChildren@shared/utils/flattenChildren.js
    

    在这里,我们需要注意传递给traverseAllChildren()的回调

    function flattenSingleChildIntoContext(
      traverseContext,
      child,
      name,
      selfDebugID
    ) {
      // We found a component instance.
      if (traverseContext && typeof traverseContext === 'object') {
        var result = traverseContext;
        var keyUnique = result[name] === undefined;
    
        // scr: DEV code
    ...
    
        if (keyUnique && child != null) {
          result[name] = child;
        }
      }
    }
    
    flattenSingleChildIntoContext@shared/utils/flattenChildren.js
    

    ,该回调将单个ReactElement及其关联的键(name)设置在对象(map)中。 接下来,我们查看traverseAllChildren()方法主体,以特别了解如何生成密钥。

    ...
    var SEPARATOR = '.';
    ...
    
    function traverseAllChildren(children, callback, traverseContext) {
      if (children == null) {
        return 0;
      }
    
      return traverseAllChildrenImpl(children, '', callback, traverseContext);
    }
    
    traverseAllChildren@shared/utils/traverseAllChildren.js
    
    function traverseAllChildrenImpl(
      children,
      nameSoFar, // scr: -------- ''
      callback,
      traverseContext
    ) {
      var type = typeof children;
    
      if (type === 'undefined' || type === 'boolean') {
        // All of the above are perceived as null.
        children = null;
      }
    
      if (children === null || type === 'string' || type === 'number' ||
    type === 'object' && children.$$typeof === REACT_ELEMENT_TYPE) {
        callback(traverseContext, children,
        // If it's the only child, treat the name as if it was wrapped in an array
        // so that it's consistent if the number of children grows.
        nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar);
        return 1;
      }
    
      var child;
      var nextName;
      var subtreeCount = 0; // Count of children found in the current subtree.
      var nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
    
      if (Array.isArray(children)) {
        for (var i = 0; i < children.length; i++) {
          child = children[i];
          nextName = nextNamePrefix + getComponentKey(child, i);
          subtreeCount += traverseAllChildrenImpl(child, nextName, callback, traverseContext);
        }
      } else {
        // scr: code that is not applicable
    ...
      }
    
      return subtreeCount;
    }
    
    traverseAllChildrenImpl@shared/utils/traverseAllChildren.js
    

    如前所述,我们在{第五篇}中描述了该方法,

    when it is called the first time (and the type of children parameter is array), it calls itself for every ReactElement within the array; when it is called successively (children is ReactElement), invokes the aforementioned callback that…
    第一次调用时(children参数的类型是array),它将为数组中的每个ReactElement调用自身; 当它被连续调用时(childrenReactElement),将调用上述回调……

    “将单个ReactElement及其相关的键(name)设置在对象中”。

    密钥是使用getComponentKey()生成的,

    function getComponentKey(component, index) {
      if (component && typeof component === 'object' && component.key != null) {
        // Explicit key
        return KeyEscapeUtils.escape(component.key);
      }
      // Implicit key determined by the index in the set
      return index.toString(36);
    }
    
    getComponentKey@shared/utils/traverseAllChildren.js
    

    如果未在“无密钥节点”中显式设置密钥,则该方法基本上使用数组的索引作为对象中的密钥(index.toString(36))。

    flattenChildren()的静态(子)调用栈,

    ...
    flattenChildren()
      |-traverseAllChildren()
        |-traverseAllChildrenImpl()
          |↻traverseAllChildrenImpl() // for direct each child
            |-flattenSingleChildIntoContext()
    

    现在我们有一个键值对象nextChildrenprevChildren进行了“区分”。

    ReactChildReconciler.updateChildren()—操作虚拟DOM树

    updateChildren: function(
      prevChildren,
      nextChildren,
      mountImages,
      removedNodes,
      transaction,
      hostParent,
      hostContainerInfo,
      context,
      selfDebugID, // 0 in production and for roots
    ) {
      if (!nextChildren && !prevChildren) {
        return;
      }
    
      var name;
      var prevChild;
    
      for (name in nextChildren) {
        if (!nextChildren.hasOwnProperty(name)) {
          continue;
        }
    
        prevChild = prevChildren && prevChildren[name];
        var prevElement = prevChild && prevChild._currentElement;
        var nextElement = nextChildren[name];
        if ( // scr: -----------------------------------------------> 1)
          prevChild != null &&
          shouldUpdateReactComponent(prevElement, nextElement)
        ) {
          ReactReconciler.receiveComponent(
            prevChild,
            nextElement,
            transaction,
            context,
          );
          nextChildren[name] = prevChild; // scr: --------------> end 1)
        } else {
          if (prevChild) { // scr: ---------------------------------> 2)
            removedNodes[name] = ReactReconciler.getHostNode(prevChild);
            ReactReconciler.unmountComponent(prevChild, false);
          }
    
          // The child must be instantiated before it's mounted.
          var nextChildInstance = instantiateReactComponent(nextElement, true);
          nextChildren[name] = nextChildInstance;
          // Creating mount image now ensures refs are resolved in right order
          // (see https://github.com/facebook/react/pull/7101 for explanation).
          var nextChildMountImage = ReactReconciler.mountComponent(
            nextChildInstance,
            transaction,
            hostParent,
            hostContainerInfo,
            context,
            selfDebugID,
          );
    
          mountImages.push(nextChildMountImage);
        } // scr: ----------------------------------------------> end 2)
      }
    
      // scr: ------------------------------------------------------> 3)
      // Unmount children that are no longer present.
      for (name in prevChildren) {
        if (
          prevChildren.hasOwnProperty(name) &&
          !(nextChildren && nextChildren.hasOwnProperty(name))
        ) {
          prevChild = prevChildren[name];
          removedNodes[name] = ReactReconciler.getHostNode(prevChild);
          ReactReconciler.unmountComponent(prevChild, false);
        }
      } // scr: ------------------------------------------------> end 3)
    },
    

    updating is nothing more than modifying, adding, and deleting
    更新无非就是修改,添加和删除

    此方法遍历nextChildren,并且

    1)如果相应的“ pre”和“ next”节点的类型相同(由shouldUpdateReactComponent()判断),则递归回到ReactReconciler.receiveComponent()以像{上一篇}中那样修改关联的DOM节点的内容。 {上一篇}),其逻辑分支适用于

    因为比较是基于对方的索引(也是key);

    2)如果“ pre”和“ next”节点的类型不同,或者相应的“ pre”节点根本不存在,则重新安装虚拟DOM;

    As in {post five}, the virtual DOM’s corresponding li node has been created in the mounting process;
    与{第五篇}中一样,虚拟DOM的相应li节点已在安装过程中创建;

    3)如果“下一个”虚拟DOM不存在,则卸载它们。

    内容更新操作封装在ReactReconciler.receiveComponent(){上一篇}的递归中,而真正的DOM树上的操作是在逻辑处理返回到ReactMultiChild.updateChildren()中时进行的。

    ReactMultiChild.updateChildren()II —构造真实的DOM

    ...
      var updates = null;
      var name;
      // `nextIndex` will increment for each child in `nextChildren`, but
      // `lastIndex` will be the last index visited in `prevChildren`.
      var nextIndex = 0;
      var lastIndex = 0;
      
      // `nextMountIndex` will increment for each newly mounted child.
      var nextMountIndex = 0;
      var lastPlacedNode = null;
      for (name in nextChildren) {
        if (!nextChildren.hasOwnProperty(name)) {
          continue;
        }
    
        // scr: --------------------------------------------------> III)
        var prevChild = prevChildren && prevChildren[name];
        var nextChild = nextChildren[name];
        if (prevChild === nextChild) {
          updates = enqueue(
                      updates,
                      this.moveChild(
                        prevChild,
                        lastPlacedNode,
                        nextIndex,
                        lastIndex
                      )
                    );
    
          lastIndex = Math.max(prevChild._mountIndex, lastIndex);
          prevChild._mountIndex = nextIndex; // scr: ---------> end III)
        } else { // scr: ------------------------------------------> IV)
          if (prevChild) {
            // Update `lastIndex` before `_mountIndex` gets unset by unmounting.
            lastIndex = Math.max(prevChild._mountIndex, lastIndex);
            // The `removedNodes` loop below will actually remove the child.
          }
    
          // The child must be instantiated before it's mounted.
          updates = enqueue(
                      updates,
                      this._mountChildAtIndex(
                        nextChild,
                        mountImages[nextMountIndex],
                        lastPlacedNode,
                        nextIndex,
                        transaction,
                        context
                      )
                    );
    
          nextMountIndex++;
        } // scr: ---------------------------------------------> end IV)
    
        nextIndex++;
        lastPlacedNode = ReactReconciler.getHostNode(nextChild);
      }
    
    // Remove children that are no longer present.
      for (name in removedNodes) { // scr: -------------------------> V)
        if (removedNodes.hasOwnProperty(name)) {
          updates = enqueue(
                      updates,
                      this._unmountChild(
                        prevChildren[name],
                        removedNodes[name]
                      )
                    );
        }
      } // scr: ------------------------------------------------> end V)
    
      if (updates) {
        processQueue(this, updates); // scr: ----------------------> VI)
      }
    
      this._renderedChildren = nextChildren;
    
      // scr: DEV code
    ...
    
    ReactMultiChild@renderers/shared/stack/reconciler/ReactMultiChild.js
    

    此逻辑块迭代nextChildren,并在必要时对其进行循环。

    III)标记节点的位置已更改;

    IV)标记一个新添加的节点;

    V)标记一个已删除的节点;

    VI)将更改提交到DOM树{上一篇}

    此处适用的分支是IV),将ReactElement [4]关联的节点添加到DOM树。

    _mountChildAtIndex: function (
      child,
      mountImage,
      afterNode,
      index,
      transaction,
      context
    ) {
      child._mountIndex = index;
      return this.createChild(child, afterNode, mountImage);
    },
    
    createChild: function (child, afterNode, mountImage) {
      return makeInsertMarkup(mountImage, afterNode, child._mountIndex);
    },
    
    function makeInsertMarkup(markup, afterNode, toIndex) {
      // NOTE: Null values reduce hidden classes.
      return {
        type: 'INSERT_MARKUP',
        content: markup,
        fromIndex: null,
        fromNode: null,
        toIndex: toIndex,
        afterNode: afterNode
      };
    }
    
    ReactMultiChild@renderers/shared/stack/reconciler/ReactMultiChild.js
    

    在VI中)

    processUpdates: function(parentNode, updates) {
      // scr: DEV code
    ...
    
    for (var k = 0; k < updates.length; k++) {
        var update = updates[k];
        switch (update.type) {
          case 'INSERT_MARKUP':
              insertLazyTreeChildAt(
                parentNode,
                update.content,
                getNodeAfter(parentNode, update.afterNode),
              );
              break;
          // scr: code that is not applicable
    ...
    
    function insertLazyTreeChildAt(
      parentNode,
      childTree,
      referenceNode
    ) {
      DOMLazyTree.insertTreeBefore(
        parentNode,
        childTree,
        referenceNode
      );
    }
    
    DOMChildrenOperations@renderers/dom/client/utils/DOMChildrenOperations.js
    

    因此,此堆栈中的最后一张卡是DOMLazyTree.insertTreeBefore()。 从{第三篇}我们已经知道,此方法调用HTML DOM API

    parentNode.insertBefore(tree.node, referenceNode);
    

    那什么时候发生

    用键区分节点

    示例2

    ...
    render() {
      return (
        <ul>
        {
          this.state.data.map(function(val, i) {
            return <li key={val}>{ val }</li>;
          })
        }
        </ul>
      );
    }
    ...
    

    过程逻辑与ReactDOMComponent.flattenChildren()之前的无键节点中的逻辑相同,在无键节点中,将使用指定的键而不是数组索引来建立键值对象,

    function getComponentKey(component, index) {
      if (component && typeof component === 'object' && 
          component.key != null) {
        // Explicit key
        return KeyEscapeUtils.escape(component.key);
      }
      // code that is not applicable
    ...
    }
    
    getComponentKey@shared/utils/traverseAllChildren.js
    

    因此,在ReactChildReconciler.updateChildren()中,可以更好地对齐两个虚拟DOM树的比较,

    并且通过比较具有相同内容且仅具有相同内容的节点(键:一和二),递归ReactReconciler.receiveComponent()不会引发任何DOM操作。 必要的DOM操作,即

    parentNode.insertBefore(tree.node, referenceNode);
    

    ReactMultiChild.updateChildren()中的节点(key:new)执行。

    其结果是,keys可以抽出一些不必要的DOM操作用于突变DOM树。

    Take home

    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          mutate: false,
        };
    
        this.timer = setInterval(
          () => this.tick(),
          5000
        );
      }
    
      tick() {
        this.setState({
          mutate: true,
        });
      }
    
      render() {
        return (
          <ul>
            { this.state.mutate &&
            <li>New</li>
            }
            <li>One</li>
            <li>Two</li>
          </ul>
        );
      }
    }
    
    export default App;
    

    上面的代码还更改了DOM树结构。 您能回答为什么这里不需要按键吗?

    —尾注—

    有目的地读取源代码就像搜索数组一样,从理论上讲,对数组进行排序后,它的读取速度为O(n)-O(log n)。 本系列旨在为您整理React代码库,因此只要有目的,您就可以享受O(log n)

    (原文链接)Understanding The React Source Code - UI Updating (DOM Tree) IX

    (上一篇)【译】了解React源代码-UI更新(单个DOM)8

    相关文章

      网友评论

          本文标题:【译】了解React源代码-UI更新(DOM树)9

          本文链接:https://www.haomeiwen.com/subject/oxeymctx.html