ホームページ  >  記事  >  ウェブフロントエンド  >  React の Diff アルゴリズムとは何ですか? Diff アルゴリズムの戦略と実装

React の Diff アルゴリズムとは何ですか? Diff アルゴリズムの戦略と実装

不言
不言転載
2018-09-28 17:27:033853ブラウズ

この記事の内容は、React の Diff アルゴリズムとは何ですか? Diff アルゴリズムの戦略と実装には一定の参考値がありますので、困っている方は参考にしていただければ幸いです。

1. Diff アルゴリズムとは

  • 従来の Diff: diff アルゴリズムは、HTML DOM 構造の差分検索アルゴリズムです。ツリーアルゴリズムの検索、および 2 つのツリーの差を計算する時間計算量は O(n^3) ですが、これは明らかにコストが高すぎるため、この従来のアルゴリズムを React が採用することは不可能です。

  • React Diff:
  • 前述したように、React は仮想 DOM テクノロジーを使用して実際の DOM をマッピングします。つまり、DOM の差分検索です。 React Diff アルゴリズムは、本質的に 2 つの JavaScript オブジェクトの差分検索を比較することです。
    • 3 つの戦略に基づいています。 Web UI での DOM ノードのレベル移動操作 非常に小さいため、無視できます。 (ツリー差分)
    • #同じクラスを持つ 2 つのコンポーネントは同様のツリー構造を生成し、異なるクラスを持つ 2 つのコンポーネントは異なるツリー構造を生成します (コンポーネント差分)

    同じレベルにある子ノードのグループは、一意の ID によって区別できます。 (要素 diff)
  1. 2. React Diff アルゴリズムの解釈

  2. まず、Diff が次のことを行うことを明確にする必要があります。 React 更新フェーズのアルゴリズムの適用中にのみ発生します。

React 更新メカニズム:

  • ##React Diff アルゴリズム最適化戦略チャート:

React の Diff アルゴリズムとは何ですか? Diff アルゴリズムの戦略と実装

    ##React 更新フェーズでは、ReactElement のタイプを決定し、さまざまな操作を実行します。ReactElement のタイプには、テキスト、Dom、およびコンポーネントの 3 つのタイプが含まれます。 :
カスタム要素の更新は主にレンダリングされたノードを更新することであり、店主はレンダリングされたノードの対応するコンポーネントに更新の管理を任せます。

React の Diff アルゴリズムとは何ですか? Diff アルゴリズムの戦略と実装テキスト ノードの更新は非常に簡単で、コピーを直接更新するだけです。

  • ブラウザの基本要素の更新は 2 つの部分に分かれています:

  • 属性の更新、以前との比較属性以降 異なる、部分的な更新。また、イベント バインディングなどの特別なプロパティを処理します。
    • 子ノードの更新は、主に差分オブジェクトを見つけるために行われます。その場合は、上記の shouldUpdateReactComponent も使用されます。直接更新できる場合は、再帰的に子ノードの更新を呼び出します。これにより、差分オブジェクトも再帰的に検索されます。以前のオブジェクトの削除や新しいオブジェクトの追加を直接更新することはできません。そして、差分オブジェクトを元にDOM要素の操作(位置変更、削除、追加等)を行います。
    • 実際、Diff アルゴリズムは React 更新フェーズの DOM 要素更新プロセス中にのみ呼び出されます。 ?

  1. #1. テキスト タイプが更新され、内容が異なる場合は、複雑な Diff アルゴリズムを呼び出すことなく、直接更新および置換されます。

     ReactDOMTextComponent.prototype.receiveComponent(nextText, transaction) {
        //与之前保存的字符串比较
        if (nextText !== this._currentElement) {
          this._currentElement = nextText;
          var nextStringText = '' + nextText;
          if (nextStringText !== this._stringText) {
            this._stringText = nextStringText;
            var commentNodes = this.getHostNode();
            // 替换文本元素
            DOMChildrenOperations.replaceDelimitedText(
              commentNodes[0],
              commentNodes[1],
              nextStringText
            );
          }
        }
      }
  2. 2. カスタム コンポーネント要素の場合:

    class Tab extends Component {
        constructor(props) {
            super(props);
            this.state = {
                index: 1,
            }
        }
        shouldComponentUpdate() {
            ....
        }
        render() {
            return (
                <p>
                    </p><p>item1</p>
                    <p>item1</p>
                
            )
        }
        
    }
明確にする必要があるのは、コンポーネントとは何かということです。コンポーネントは単なる Html 構造のパッケージング コンテナであり、この Html 構造のステータスを管理する機能があります。たとえば、上記の Tab コンポーネント: その重要な内容です。は render 関数によって返される Html 構造であり、Tab クラスと呼ばれるものは、この Html 構造のパッケージング コンテナーです (
  • ご存知のとおり、パッケージング ボックスとして理解できます)。 React レンダリング メカニズムの図を参照してください。カスタム コンポーネントは最終的に React Diff 最適化戦略 1 と結合されます (異なるクラスの 2 つのコンポーネントは異なる構造を持っています)

3 基本要素:

ReactDOMComponent.prototype.receiveComponent = function(nextElement, transaction, context) {
    var prevElement = this._currentElement;
    this._currentElement = nextElement;
    this.updateComponent(transaction, prevElement, nextElement, context);
}

ReactDOMComponent.prototype.updateComponent = function(transaction, prevElement, nextElement, context) {
    //需要单独的更新属性
    this._updateDOMProperties(lastProps, nextProps, transaction, isCustomComponentTag);
    //再更新子节点
    this._updateDOMChildren(
      lastProps,
      nextProps,
      transaction,
      context
    );

    // ......
}

この中で、diff アルゴリズムは _updateDOMChildren メソッドで内部的に呼び出されます。

  • 3. React での Diff アルゴリズムの実装

    _updateChildren: function(nextNestedChildrenElements, transaction, context) {
        var prevChildren = this._renderedChildren;
        var removedNodes = {};
        var mountImages = [];
    
        // 获取新的子元素数组
        var nextChildren = this._reconcilerUpdateChildren(
          prevChildren,
          nextNestedChildrenElements,
          mountImages,
          removedNodes,
          transaction,
          context
        );
    
        if (!nextChildren && !prevChildren) {
          return;
        }
    
        var updates = null;
        var name;
        var nextIndex = 0;
        var lastIndex = 0;
        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) {
            // 同一个引用,说明是使用的同一个component,所以我们需要做移动的操作
            // 移动已有的子节点
            // NOTICE:这里根据nextIndex, lastIndex决定是否移动
            updates = enqueue(
              updates,
              this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex)
            );
    
            // 更新lastIndex
            lastIndex = Math.max(prevChild._mountIndex, lastIndex);
            // 更新component的.mountIndex属性
            prevChild._mountIndex = nextIndex;
    
          } else {
            if (prevChild) {
              // 更新lastIndex
              lastIndex = Math.max(prevChild._mountIndex, lastIndex);
            }
    
            // 添加新的子节点在指定的位置上
            updates = enqueue(
              updates,
              this._mountChildAtIndex(
                nextChild,
                mountImages[nextMountIndex],
                lastPlacedNode,
                nextIndex,
                transaction,
                context
              )
            );
    
    
            nextMountIndex++;
          }
    
          // 更新nextIndex
          nextIndex++;
          lastPlacedNode = ReactReconciler.getHostNode(nextChild);
        }
    
        // 移除掉不存在的旧子节点,和旧子节点和新子节点不同的旧子节点
        for (name in removedNodes) {
          if (removedNodes.hasOwnProperty(name)) {
            updates = enqueue(
              updates,
              this._unmountChild(prevChildren[name], removedNodes[name])
            );
          }
        }
      }
    4. Diff に基づく開発提案
  • ツリー差分に基づいてコンポーネントを開発する場合:

、DOM 構造の安定性を維持することに注意してください。つまり、DOM 構造の動的操作をできる限り少なくすることです (特にモバイル)。オペレーション。

    ノードの数が多すぎる場合、またはページの更新回数が多すぎる場合、ページの遅延がより顕著になります。
  • 現時点では、DOM ノードを実際に削除または追加する代わりに、CSS を使用してノードを非表示または表示できます。

    コンポーネントの差分に基づく
  • :

  1. コンポーネントの不要な更新を減らすために shouldComponentUpdate() の使用に注意してください。

  2. 同様の構造は可能な限りコンポーネントにカプセル化する必要があります。これにより、コードの量が削減されるだけでなく、コンポーネント diff のパフォーマンス消費も削減されます。

  • 要素の差分に基づく:

    1. リスト構造の場合は、最後の要素を減らすようにしてください。ノード数が多すぎる場合や更新頻度が高すぎる場合、ノードをリストの先頭に移動する操作は React のレンダリング パフォーマンスにある程度影響します。

    以上がReact の Diff アルゴリズムとは何ですか? Diff アルゴリズムの戦略と実装の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。