ホームページ  >  記事  >  ウェブフロントエンド  >  React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説

React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説

寻∝梦
寻∝梦オリジナル
2018-09-11 14:58:161703ブラウズ

この記事では、主に react フレームワークの原理について詳しく説明します。以下では、react について詳しく説明します。今すぐこの記事を見てみましょう

私は React に 2 年以上取り組んできましたが、このフレームワークの利点は誰もがよく知っていますが、大規模なプロジェクトでは、その欠点が徐々に明らかになります。 >Redux 、 ReactRouter 、およびその他のサードパーティ フレームワークを使用すると、複雑なビジネス コードの量が非常に大きくなります (フロントエンド コードは通常、以前のサイズの 1.5 倍になります)。初期段階で基礎となる設計が良くないと、開発効率が低いという問題に直面することがよくあります。以下は、React フレームワークの核となる概念をいくつかまとめたものです。皆様のお役に立てれば幸いです: ReduxReactRouter等三方框架后,结合复杂的业务代码量会变得非常大(前端代码常常是以前的1.5倍)。如果前期底层设计得不好,时常面临着开发效率低的问题。下面归纳了一些React框架的核心概念,希望对大家有帮助:

React diff 算法

React的diff算法是Virtual DOM之所以任性的最大依仗,大家知道页面的性能 一般是由渲染速度和渲染次数决定,如何最大程度地利用diff算法进行开发?我们先看看它的原理。

传统 diff 算法

计算一棵树形结构转换成另一棵树形结构的最少操作,传统 diff 算法通过循环递归对节点进行依次对比,效率低下,算法复杂度达到 O(n^3),其中 n 是树中节点的总数。也就是说如果要展示1000个节点,就要依次执行上十亿次的比较。这个性能消耗对对于前端项目来说是不可接受的。

核心算法

如上所见,传统 diff 算法的复杂度为 O(n^3),显然这是无法满足性能要求的。而React通过制定大胆的策略,将 O(n^3) 复杂度的问题转换成 O(n) 复杂度的问题。他是怎么做到的?

tree diff

Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。React 对树的算法进行了简洁明了的优化,即对树进行分层比较,两棵树只会对同一层次的节点进行比较。如下图所示:

React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説

React 通过 updateDepth 对 Virtual DOM 树进行层级控制,只会对相同颜色方框内的 DOM 节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。

// tree diff算法实现updateChildren: function(nextNestedChildrenElements, transaction, context) {
  updateDepth++;  var errorThrown = true;  try {    this._updateChildren(nextNestedChildrenElements, transaction, context);
    errorThrown = false;
  } finally {
    updateDepth--;    if (!updateDepth) {      if (errorThrown) {
        clearQueue();
      } else {
        processQueue();
      }
    }
  }
}

为什么要减少DOM节点的跨层级操作?

如下图,A 节点(包括其子节点)整个被移动到 D 节点下,由于 React 只会简单的考虑同层级节点的位置变换,而对于不同层级的节点,只有创建和删除操作。当根节点发现子节点中 A 消失了,就会直接销毁 A;当 D 发现多了一个子节点 A,则会创建新的 A(包括子节点)作为其子节点。此时,React diff 的执行情况:create A -> create B -> create C -> delete A。

React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説

由此可发现,当出现节点跨层级移动时,并不会出现想象中的移动操作,而是以 A 为根节点的树被整个重新创建,这是一种影响 React 性能的操作。

component diff

拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。

  • 如果是同一类型的组件,按照原策略继续比较 virtual DOM tree

  • 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。

  • 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate()

    React diff アルゴリズム

React の diff アルゴリズムは Virtual DOM が任意である最大の理由は、ページのパフォーマンスが一般にレンダリング速度とレンダリング数によって決まることを誰もが知っているからです。 diff 開発用アルゴリズム?まずはその仕組みを見てみましょう。 <p></p> <h2>従来の差分アルゴリズム</h2> <img src="https://img.php.cn//upload/image/667/341/244/1536648833324341.png" title="1536648833324341.png" alt="React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説"> あるツリー構造を別のツリー構造に変換するために必要な最小限の操作を計算します。 従来の差分アルゴリズムはループ再帰によってノードを順次比較しますが、これは非効率であり、アルゴリズムの複雑さは O に達します。 (n^3)、n はツリー内のノードの総数です。つまり、1,000 個のノードを表示したい場合は、数十億回の比較を順番に実行する必要があります。このようなパフォーマンスの消費は、フロントエンド プロジェクトでは許容できません。

コア アルゴリズム

🎜 上で見たように、従来の差分アルゴリズムの複雑さは O(n^3) であり、明らかに要件を満たすことができません。パフォーマンス要件が必要です。そして React は、大胆な戦略を立てることによって、O(n^3) の複雑さの問題を O(n) の複雑さの問題に変換します。彼はどうやってそんなことをしたのでしょうか? 🎜

tree diff

🎜 Web UI では DOM ノードのクロスレベル移動操作はほとんどないため、無視できます。 React はツリー アルゴリズムを簡潔かつ明確に最適化しました。つまり、2 つのツリーは同じレベルのノードのみを比較します。下の図に示すように: 🎜🎜62 .png 🎜🎜🎜React は、updateDepth を使用して仮想 DOM ツリーを階層的に制御します。同じカラー ボックス内の DOM ノード、つまり、同じ親ノードの下にあるすべての子ノードのみを比較します。ノードがもう存在しないことが判明した場合、そのノードとそのサブノードは完全に削除され、それ以上の比較には使用されません。このようにして、DOM ツリー全体の比較を完了するには、ツリーを 1 回走査するだけで済みます。 🎜
// 卸载组件unmountComponent: function() {
  // 设置状态为 UNMOUNTING
  this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING;  // 如果存在 componentWillUnmount,则触发
  if (this.componentWillUnmount) {    this.componentWillUnmount();
  }  // 更新状态为 null
  this._compositeLifeCycleState = null;  this._renderedComponent.unmountComponent();  this._renderedComponent = null;

  ReactComponent.Mixin.unmountComponent.call(this);
}

DOM ノードのクロスレベル操作を減らす必要があるのはなぜですか?

🎜以下の図に示すように、React は同じレベルのノードの位置変換のみを考慮するため、A ノード (そのサブノードを含む) は完全に D ノードに移動されます。レベルが異なる場合は、作成操作と削除操作のみが行われます。ルートノードは、A が子ノード内で消失していることを検出すると、A を直接破棄します。D は、追加の子ノード A があることを検出すると、その子ノードとして新しい A (子ノードを含む) を作成します。このとき、React diff の実行ステータスは、create A -> create B -> create C -> delete A になります。 🎜🎜React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説🎜🎜ノードがレベル間を移動する場合、仮想の移動操作は発生しませんが、ルート ノードとして A を持つツリーが完全に再作成されることがわかります。これは、React パフォーマンス操作に影響します。 。 🎜

component diff

🎜同じクラスを持つ 2 つのコンポーネントは同様のツリー構造を生成し、異なるクラスを持つ 2 つのコンポーネントは異なるツリー構造を生成します。 🎜
  • 🎜 同じタイプのコンポーネントの場合は、元の戦略に従って 仮想 DOM ツリー の比較を続けます。 🎜🎜
  • 🎜そうでない場合、コンポーネントは ダーティ コンポーネント と判断され、コンポーネント全体の下にあるすべての子ノードが置き換えられます。 🎜🎜
  • 🎜同じタイプのコンポーネントの場合、その Virtual DOM に変更がない可能性があります。これを確実に把握できれば、差分操作の時間を大幅に節約できます。したがって、 React により、ユーザーは shouldComponentUpdate() を使用して、コンポーネントの差分を取得する必要があるかどうかを判断できるようになります。 🎜🎜🎜🎜🎜🎜

    上の図に示すように、コンポーネント Dコンポーネント G に変更されると、2 つの コンポーネント が似た構造であっても、一度 >React > D と G が異なる種類のコンポーネントであると判断された場合、両者の構造は比較されませんが、component D は直接削除され、component G とその子ノードが再作成されます。 React diff は、2 つの component の型は異なるが構造が類似している場合、パフォーマンスに影響しますが、React 公式ブログに記載されているように、次のようになります。 コンポーネントDOM ツリー と類似する機会はほとんどないため、この極端な要因が実装および開発プロセスに重大な影響を与えることは困難です。 component D 改变为 component G 时,即使这两个 component 结构相似,一旦 React 判断 D 和 G 是不同类型的组件,就不会比较二者的结构,而是直接删除 component D,重新创建 component G 以及其子节点。虽然当两个 component 是不同类型但结构相似时,React diff 会影响性能,但正如 React 官方博客所言:不同类型的 component 是很少存在相似 DOM tree 的机会,因此这种极端因素很难在实现开发过程中造成重大影响的。

    element diff

    对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。React 提出优化策略:允许开发者对同一层级的同组子节点,添加唯一 key 进行区分,虽然只是小小的改动,性能上却发生了翻天覆地的变化!

    新老集合所包含的节点,如下图所示,新老集合进行 diff 差异化对比,通过 key 发现新老集合中的节点都是相同的节点,因此无需进行节点删除和创建,只需要将老集合中节点的位置进行移动,更新为新集合中节点的位置,此时 React 给出的 diff 结果为:B、D 不做任何操作,A、C 进行移动操作,即可。

    React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説

    开发建议

    (1)[基于tree diff] 开发组件时,保持稳定的DOM结构有助于维持整体的性能。换而言之,尽可能少地动态操作DOM结构,尤其是移动操作。当节点数过大或者页面更新次数过多时,页面卡顿的现象比较明显。可以通过 CSS 隐藏或显示节点,而不是真的移除或添加 DOM 节点。

    (2)[基于component diff] 开发组件时,注意使用 shouldComponentUpdate() 来减少组件不必要的更新。除此之外,对于类似的结构应该尽量封装成组件,既减少代码量,又能减少component diff的性能消耗。

    (3)[基于element diff] 对于列表结构,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。

    React Lifecycle

    React的生命周期具体可分为四种情况:

    React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説

    • 当首次装载组件时,按顺序执行 getDefaultPropsgetInitialStatecomponentWillMountrendercomponentDidMount

    • 当卸载组件时,执行 componentWillUnmount

    • 当重新装载组件时,此时按顺序执行 getInitialStatecomponentWillMountrendercomponentDidMount,但并不执行 getDefaultProps

    • 当再次渲染组件时,组件接受到更新状态,此时按顺序执行 componentWillReceivePropsshouldComponentUpdatecomponentWillUpdaterendercomponentDidUpdate

    React组件的3种状态

    状态一:MOUNTING

    mountComponent 负责管理生命周期中的 getInitialStatecomponentWillMountrendercomponentDidMount

    React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説

    状态二:RECEIVE_PROPS

    updateComponent 负责管理生命周期中的 componentWillReceivePropsshouldComponentUpdatecomponentWillUpdaterendercomponentDidUpdate

    React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説

    状态三:UNMOUNTING

    unmountComponent 负责管理生命周期中的 componentWillUnmount。(想看更多就到PHP中文网React参考手册栏目中学习)

    首先将状态设置为 UNMOUNTING,若存在 componentWillUnmount,则执行;如果此时在 componentWillUnmount 中调用 setState,是不会触发 reRender。更新状态为 NULL

    要素の差分

    同じレベルにある子ノードのグループは、一意の ID によって区別できます。 React は最適化戦略を提案しています。開発者は、同じレベルの同じグループの子ノードを区別するために一意のキーを追加することができます。これはほんの小さな変更ですが、パフォーマンスは劇的に変化しました。 🎜🎜新旧セットに含まれるノードは下図の通りです。キーにより新旧セット内のノードが同一のノードであることが分かります。ノードを削除して作成する必要はありません。古いセット内のノードの位置が新しいセット内のノードの位置に移動および更新されるだけです。このとき、React によって得られる diff 結果は次のようになります。 :BとDは何も操作せず、AとCは移動操作を行います。 🎜🎜React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説🎜開発の提案🎜(1)[ツリーの差分に基づく] コンポーネントを開発する場合、安定した DOM 構造を維持することは、全体的なパフォーマンスの維持に役立ちます。言い換えれば、DOM 構造の動的操作、特に移動操作はできるだけ少なくしてください。ノードの数が多すぎる場合、またはページの更新数が多すぎる場合、ページ ラグの現象が顕著になります。実際に DOM ノードを削除または追加しなくても、CSS を介してノードを非表示または表示できます。 🎜🎜(2) [コンポーネントの差分に基づく] コンポーネントを開発するときは、コンポーネントの不要な更新を減らすために shouldComponentUpdate() の使用に注意してください。さらに、同様の構造を可能な限りコンポーネントにカプセル化する必要があります。これにより、コードの量が削減されるだけでなく、component diff のパフォーマンス消費も削減されます。 🎜🎜(3) [要素の差分に基づく] リスト構造の場合、ノードの数が多すぎる場合や更新操作が面倒な場合は、最後のノードをリストの先頭に移動するなどの操作を減らすようにしてください。頻度が高すぎると、React のレンダリング パフォーマンスにある程度影響します。 🎜

    React ライフサイクル

    🎜 React のライフサイクルは 4 つの状況に分類できます: 🎜🎜React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説🎜
    • 🎜 コンポーネントが初めてロードされるときは、 order getDefaultPropsgetInitialStatecomponentWillMountrender、および componentDidMount 🎜
    • >
    • 🎜コンポーネントをアンロードする場合は、componentWillUnmount を実行します 🎜
    • 🎜 コンポーネントをリロードする場合は、getInitialStatecomponentWillMount、<code>render、および componentDidMount を順序付けしますが、getDefaultProps は実行されません 🎜
    • 🎜 コンポーネントがレンダリングされるとき。再度、コンポーネントは更新されたステータスを受け取り、componentWillReceivePropsShouldComponentUpdatecomponentWillUpdaterender、および を実行します。シーケンス。componentDidUpdate。 🎜

    React コンポーネントの 3 つの状態

    状態 1: MOUNTING

    🎜mountComponentgetInitialStatecomponentWillMountrender、および componentDidMount。 🎜🎜React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説🎜状態 2: RECEIVE_PROPS🎜updateComponent は、componentWillReceiveProps shouldComponentUpdatecomponentWillUpdate の管理を担当します。ライフサイクル >、render、および componentDidUpdate。 🎜🎜React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説🎜状態 3: アンマウント中🎜unmountComponent は、ライフサイクル内の componentWillUnmount を管理します。 (さらに詳しく知りたい場合は、PHP 中国語 Web サイト React リファレンス マニュアル にアクセスして学習してください) ) 🎜🎜 まず状態を UNMOUNTING に設定します。componentWillUnmount が存在する場合は、componentWillUnmountsetState が呼び出された場合に実行します。現時点では、reRender はトリガーされません。更新ステータスは NULL で、コンポーネントのアンインストール操作は完了しました。実装コードは次のとおりです: 🎜
    // 卸载组件unmountComponent: function() {
      // 设置状态为 UNMOUNTING
      this._compositeLifeCycleState = CompositeLifeCycle.UNMOUNTING;  // 如果存在 componentWillUnmount,则触发
      if (this.componentWillUnmount) {    this.componentWillUnmount();
      }  // 更新状态为 null
      this._compositeLifeCycleState = null;  this._renderedComponent.unmountComponent();  this._renderedComponent = null;
    
      ReactComponent.Mixin.unmountComponent.call(this);
    }

    React生命周期总结

    React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説

    生命周期 调用次数 能否使用setState()
    getDefaultProps 1
    getInitialState 1
    componentWillMount 1
    render >=1
    componentDidMount 1
    componentWillReceiveProps >=0
    shouldComponentUpdate >=0
    componentWillUpdate >=0
    componentDidUpdate >=0
    componentWillUnmount 1
    componentDidUnmount 1

    setState实现机制

    setStateReact框架的核心方法之一,下面介绍一下它的原理:

    React フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説

    // 更新 statesetState: function(partialState, callback) {
      // 合并 _pendingState
      this.replaceState(
        assign({}, this._pendingState || this.state, partialState),
        callback
      );
    },

    当调用 setState 时,会对 state 以及 _pendingState 更新队列进行合并操作,但其实真正更新 state 的幕后黑手是replaceState

    // 更新 statereplaceState: function(completeState, callback) {
      validateLifeCycleOnReplaceState(this);  // 更新队列
      this._pendingState = completeState;  // 判断状态是否为 MOUNTING,如果不是,即可执行更新
      if (this._compositeLifeCycleState !== CompositeLifeCycle.MOUNTING) {
        ReactUpdates.enqueueUpdate(this, callback);
      }
    },

    replaceState 会先判断当前状态是否为 MOUNTING,如果不是即会调用 ReactUpdates.enqueueUpdate 执行更新。

    当状态不为 MOUNTINGRECEIVING_PROPS 时,performUpdateIfNecessary 会获取 _pendingElement_pendingState_pendingForceUpdate,并调用 updateComponent 进行组件更新。

    // 如果存在 _pendingElement、_pendingState、_pendingForceUpdate,则更新组件performUpdateIfNecessary: function(transaction) {
      var compositeLifeCycleState = this._compositeLifeCycleState;  // 当状态为 MOUNTING 或 RECEIVING_PROPS时,则不更新
      if (compositeLifeCycleState === CompositeLifeCycle.MOUNTING ||
          compositeLifeCycleState === CompositeLifeCycle.RECEIVING_PROPS) {    return;
      }  var prevElement = this._currentElement;  var nextElement = prevElement;  if (this._pendingElement != null) {
        nextElement = this._pendingElement;    this._pendingElement = null;
      }  // 调用 updateComponent
      this.updateComponent(
        transaction,
        prevElement,
        nextElement
      );
    }

    如果在 shouldComponentUpdatecomponentWillUpdate 中调用 setState,此时的状态已经从 RECEIVING_PROPS -> NULL,则 performUpdateIfNecessary 就会调用 updateComponent 进行组件更新,但 updateComponent 又会调用 shouldComponentUpdatecomponentWillUpdate,因此造成循环调用,使得浏览器内存占满后崩溃。

    开发建议

    不建议在 getDefaultPropsgetInitialStateshouldComponentUpdatecomponentWillUpdaterendercomponentWillUnmount 中调用 setState,特别注意:不能在 shouldComponentUpdatecomponentWillUpdate中调用 setState,会导致循环调用。

    本篇文章到这就结束了(想看更多就到PHP中文网React使用手册栏目中学习),有问题的可以在下方留言提问。

以上がReact フレームワークにはどのようなアルゴリズムがありますか? Reactフレームワークのアルゴリズムを詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。