ホームページ > 記事 > ウェブフロントエンド > Reactの仮想domとdiffアルゴリズムの解説(コード付き)
この記事では、React の仮想 dom および diff アルゴリズムについて説明します (コード付き)。必要な方は参考にしていただければ幸いです。 。
仮想 dom
Jsx は表面上に HTML を書き込みますが、実際には内部で js の一部を実行します
createElement
React.createElement( type, [props], [...children] )
createElement はこのツリー構造をメモリに保存します
Jsx は最終的に、そのようなオブジェクトをメモリに再帰的に保存し、diff アルゴリズムを実行します。
シンプルcreateElement は
#reactElement を実装します - このノードを記述するためにオブジェクトが生成されます
##react diff
従来のツリーの差分との違い
計算あるツリー構造を別のツリー構造に変換するために必要な最小限の操作は複雑であり、研究する価値のある問題です。従来の差分アルゴリズムはループ再帰によってノードを順番に比較しますが、これは非効率であり、アルゴリズムの複雑さは O(n^3)
react diff 戦略に達します。 ##Web UI における DOM ノードのクロスレベル移動操作は非常に少ないため、無視できます。
同じクラスを持つ 2 つのコンポーネントは同様のツリー構造を生成し、異なるクラスを持つ 2 つのコンポーネントは異なるツリー構造を生成します。
同じレベルにある子ノードのグループは、一意の ID によって区別できます。
戦略 1 に基づいて、2 つのツリーは同じレベルのノードのみを階層的に比較します。 React は updateDepth を使用して、同じ親ノードの下にあるすべての子ノードである Virtual DOM ツリーの階層制御を実行します。
DOM ノードのレベル間移動操作とは何ですか? A ノード全体 (その子ノードを含む) が D ノードに移動されます
DOM ノードの場合React diff はレベル間の移動操作でどのように実行されますか? React は同じレベルのノードの位置変更のみを考慮しますが、異なるレベルのノードの場合は作成と削除の操作のみが行われます。
ルート ノードは、子ノードの A が消滅したことを検出すると、A を直接破棄し、D が追加の子ノード A があることを検出すると、新しい A (子ノードを含む) を作成します。ノード) をその子ノードとして使用します。このときの React diff の実行は、 create A -> create B -> create C -> delete A です。
注:
コンポーネントを開発する場合、安定した DOM 構造を維持するとパフォーマンスの向上に役立ちます。たとえば、実際に DOM ノードを削除または追加しなくても、CSS を介してノードを非表示または表示できます。
コンポーネントの差分
戦略 2 に基づく
同じタイプのコンポーネントの場合は、次の手順に進みます。元の戦略の仮想 DOM ツリーに従って比較します。
そうでない場合、コンポーネントはダーティ コンポーネントと判断され、コンポーネント全体の下にあるすべての子ノードが置き換えられます。
同じ種類のコンポーネントの場合、その仮想 DOM に変更がない可能性があります。これを確実に把握できれば、React の差分操作時間を大幅に節約できます。これにより、ユーザーは shouldComponentUpdate( ) を渡して、コンポーネントの差分を取得する必要があるかどうかを判断できるようになります。
React は、D と G が異なるタイプのコンポーネントであると判断するため、2 つの構造を比較せず、コンポーネント D を直接削除し、コンポーネント G とその子ノードを再作成します。たとえ D G の構造と非常によく似ています
##要素の差分
##ノードが同レベル、React diff INSERT_MARKUP (挿入)、MOVE_EXISTING (移動)、REMOVE_NODE (削除) の 3 つのノード操作が提供されます。INSERT_MARKUP,新的 component 类型不在老集合里, 即是全新的节点,需要对新节点执行插入操作。
MOVE_EXISTING,在老集合有新 component 类型,且 element 是可更新的类型,generateComponentChildren 已调用 receiveComponent,这种情况下 prevChild=nextChild,就需要做移动操作,可以复用以前的 DOM 节点。
REMOVE_NODE,老 component 类型,在新集合里也有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作,或者老 component 不在新集合里的,也需要执行删除操作。
eg: 新老集合进行 diff 差异化对比,发现 B != A,则创建并插入 B 至新集合,删除老集合 A;以此类推,创建并插入 A、D 和 C,删除 B、C 和 D。
带来的问题:都是相同的节点,但由于位置发生变化,导致需要进行繁杂低效的删除、创建操作,其实只要对这些节点进行位置移动即可
react优化策略:允许开发者对同一层级的同组子节点,添加唯一 key 进行区分
优化后diff实现:
对新集合的节点进行循环遍历,通过唯一 key 可以判断新老集合中是否存在相同的节点
如果存在相同节点,则进行移动操作,但在移动前需要将当前节点在老集合中的位置child._mountIndex与lastIndex(访问过的节点在老集合中最右的位置即最大的位置)进行比较,if (child._mountIndex
分析:
element _mountIndex lastIndex nextIndex enqueueMove B 1 0 0 false A 0 1 1 true D 3 1 2 false C 2 3 3 true
step:
从新集合中取得 B,判断老集合中存在相同节点 B
B 在老集合中的位置 B._mountIndex = 1
初始 lastIndex = 0
不满足 child._mountIndex 更新 lastIndex = Math.max(prevChild._mountIndex, lastIndex) lastIndex更新为1
将 B 的位置更新为新集合中的位置prevChild._mountIndex = nextIndex,此时新集合中 B._mountIndex = 0,nextIndex++
以上主要分析新老集合中存在相同节点但位置不同时,对节点进行位置移动的情况,如果新集合中有新加入的节点且老集合存在需要删除的节点,那么 React diff 又是如何对比运作的呢?
element _mountIndex lastIndex nextIndex enqueueMove B 1 0 0 false E no exist C 2 1 2 false A 0 2 3 true
step
新建:从新集合中取得 E,判断老集合中不存在相同节点 E,则创建新节点 Ereact diff的问题
理论上 diff 应该只需对 D 执行移动操作,然而由于 D 在老集合的位置是最大的,导致其他节点的 _mountIndex
建议:在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。
总结:
React 通过制定大胆的 diff 策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;
React 通过分层求异的策略,对 tree diff 进行算法优化;
React 通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对 component diff 进行算法优化;
React 通过设置唯一 key的策略,对 element diff 进行算法优化;
建议,在开发组件时,保持稳定的 DOM 结构会有助于性能的提升;
開発プロセス中、ノード数が多すぎる場合や更新操作が多すぎる場合は、最後のノードをリストの先頭に移動するなどの操作を最小限に抑えることをお勧めします。 React のレンダリングのパフォーマンスにある程度影響します。
以上がReactの仮想domとdiffアルゴリズムの解説(コード付き)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。