React と DOM - ノード削除アルゴリズム

React と DOM - ノード削除アルゴリズム

[[378076]]

これは、React DOM 操作を詳細に説明した最初の記事です。記事の内容はコミット フェーズで発生します。

Fiber アーキテクチャでは、React が 2 種類のツリー構造を維持する必要があります。1 つは Fiber ツリー、もう 1 つは DOM ツリーです。 DOM ノードが削除されると、ファイバー ツリーも同期的に変更される必要があります。ただし、削除操作のタイミングには注意してください。DOM ノードの他の変更 (追加、修正) を完了する前に、他の操作への干渉を避けるために、まずファイバー ノードを削除してください。 これは、他の DOM 操作を実行するときにファイバー ツリーをループする必要があるためです。削除する必要があるのに削除されていないファイバー ノードがあると、混乱が生じます。

  1. 関数 commitMutationEffects(  
  2. firstChild: ファイバー、  
  3. ルート: FiberRoot、  
  4. レンダリング優先度レベル、  
  5. ){  
  6. fiber = firstChildとします  
  7. while (ファイバー !== null) {
  8.   // 最初の削除 
  9. const削除=ファイバー.削除;  
  10. if (削除 !== null) {  
  11. 削除をコミットします。ルート、レンダリング優先度レベル。  
  12. }  
  13. // 削除後もファイバーに子ノードが残っている場合、  
  14. // commitMutationEffectsを再帰的に呼び出して処理する 
  15. (fiber.child !== null)の場合{  
  16. const primarySubtreeTag = fiber.subtreeTag & MutationSubtreeTag;  
  17. if (プライマリサブツリータグ !== サブツリータグなし) {  
  18. コミットMutationEffects(fiber.child、ルート、renderPriorityLevel);  
  19. }  
  20. }  
  21. __DEV__ の場合: ...  
  22. // その他のDOM操作を実行する 
  23. 試す {  
  24. commitMutationEffectsImpl(ファイバー、ルート、レンダリング優先度レベル);  
  25. } キャッチ(エラー){  
  26. コミットフェーズエラーをキャプチャします(ファイバー、エラー);  
  27. }  
  28. }  
  29. ファイバーファイバー= ファイバー.sibling;  
  30. }  
  31. }

Fiber.deletions はレンダリング フェーズの diff プロセスです。ファイバーの子ノードを削除する必要がある場合は、ここで追加されます。

commitDeletion 関数はノードを削除するためのエントリ ポイントであり、unmountHostComponents を呼び出すことによって削除を実装します。削除操作を理解する前に、シーンを見てみましょう。

以下のようにファイバーツリーがあり、Node(Nodeはコード名であり、特定のノードを指すものではありません)ノードが削除されようとしています。

  1. ファイバーツリー 
  2. div#ルート 
  3. |  
  4. <アプリ/>    
  5. |  
  6. 分割 
  7. |  
  8. </>    
  9. |  
  10. ノード 
  11. | ↖  
  12. | ↖  
  13. P —————— >   <子ども>    
  14. |  
  15. 1つの

このシナリオから、ノードが削除されると、その下のサブツリー内のすべてのノードが削除されることが推測できます。それでは、このシナリオを例に、削除プロセスを見ていきましょう。このプロセスは、実際には unmountHostComponents 関数の動作メカニズムです。

削除プロセス

Node ノードを削除するには、親 DOM ノードの参加が必要です。

  1. 親インスタンス.removeChild(子)

したがって、まず親ノードを見つける必要があります。このプロセスは、ファイバー ツリー内のノードの親ノードから上方向に検索し、最初に見つかったネイティブ DOM ノードが親ノードになります。この例では、親ノードは div です。その後、ノードから始めて、ファイバー ツリーでもあるサブツリーをトラバースします。トラバースは深さ優先トラバーサルであるため、各子ノードを削除します。

特に注意が必要なのは、ループ ノードを削除するときに、各ノードが削除操作によって処理されることです。ここでの各ノードは、DOM ノードではなくファイバー ノードです。 DOM ノードは、ノードのトラバースと削除を開始すると削除されます。最初のネイティブ DOM ノード (HostComponent または HostText) が検出されると、そのサブツリーのすべてのファイバー ノードが削除された後にのみ削除されます。

上記はプロセス全体の簡単な説明です。詳細なプロセスについては、いくつかの主要な機能の責任と呼び出し関係を明確にする必要があります。 unmountHostComponents 関数はファイバー ノードを削除します。削除されたノードはターゲット ノードと呼ばれ、その役割は次のとおりです。

  1. DOMレベルで対象ノードの親ノードを見つける
  2. 対象ノードがネイティブ DOM ノードの場合は、3 と 4 を実行します。それ以外の場合は、まずノードをアンインストールしてから、ネイティブ DOM ノードを見つけて、3 と 4 を実行します。
  3. サブツリーをトラバースしてファイバーノードのアンロードを実行する
  4. 対象ノードのDOMノードを削除する

ステップ 3 の操作は、サブツリーをトラバースしてノードをアンマウントするという単純で明確な責任を持つ commitNestedUnmounts によって完了します。

その後、commitUnmount によって各ノードの特定のアンマウント プロセスが完了します。その責任は

  1. Refのアンインストール
  2. クラスコンポーネントのライフサイクル呼び出し
  3. HostPortalタイプのファイバーノードは、unmountHostComponentsを再帰的に呼び出してプロセスを繰り返し削除します。

さまざまな種類のコンポーネントの具体的な削除プロセスを見てみましょう。

削除されたコンポーネントのカテゴリを区別する

ノード タイプにはさまざまなものがあります。削除プロセスを説明するために、最も一般的な 3 つのタイプ (HostComponent、ClassComponent、HostPortal) を例に挙げます。

まず、unmountHostComponents を実行します。これは、DOM レベルで親ノードを見つけ、次の 3 つのコンポーネント タイプに従って処理します。1 つずつ見ていきましょう。

ホストコンポーネント

Node が HostComponent の場合、commitNestedUnmounts が呼び出され、Node から開始してサブツリーをトラバースし、すべての子ファイバーのアンマウントを開始します。トラバーサル プロセスは深さ優先トラバーサルです。

  1. 展開 -- >ノード(span)  
  2. | ↖  
  3. | ↖  
  4. P —————— >   <子ども>    
  5. |  
  6. 1つの

アンインストールするには、各ノードで commitUnmount を 1 つずつ実行します。このトラバーサル プロセスは、実際には 3 種類のノードで同様です。スペースを節約するために、ここでは 1 回だけ説明します。

Node のファイバーがアンロードされ、次に下方向に p のファイバーがアンロードされます。p には子がなく、その兄弟である <Child> が見つかります。<Child> のファイバーがアンロードされ、次に下方向に a が見つかり、a のファイバーがアンロードされます。この時点で、サブツリー全体のリーフ ノードに到達し、上方向への戻りを開始します。 a から <Child> へ、そして Node に戻ると、トラバーサルとアンインストールのプロセスが終了します。

サブツリーのすべてのファイバー ノードがアンロードされた後にのみ、ノードの DOM ノードを親ノードから安全に削除できます。

クラスコンポーネント

  1. 削除 -- >ノード(ClassComponent)  
  2. |  
  3. |  
  4. スパン 
  5. | ↖  
  6. | ↖  
  7. P —————— >   <子ども>    
  8. |  
  9. 1つの

ノードは ClassComponent です。対応する DOM ノードはありません。最初に commitUnmount を呼び出して自身をアンインストールする必要があります。次に、下方を調べて最初のネイティブ DOM タイプ ノード span を見つけ、それを開始点としてサブツリーをトラバースし、すべてのファイバー ノードがアンインストールされていることを確認し、親ノードから span を削除します。

ホストポータル

  1. div2(ノードのコンテナ)  
  2.  
  3. div コンテナ情報 
  4. | ↗  
  5. | ↗  
  6. ノード(ホストポータル)  
  7. |  
  8. |  
  9. スパン 
  10. | ↖  
  11. | ↖  
  12. P —————— >   <子ども>    
  13. |  
  14. 1つの

ノードはHostPortalで、対応するDOMノードがないため、削除プロセスは基本的にClassComponentと同じです。違いは、その下の最初の子ファイバーのDOMノードを削除するときに、削除されたHostPortalタイプのノードのDOMレベルの親ノードから削除されるのではなく、図のdiv2であるHostPortalのcontainerInfoから削除されることです。これは、HostPortalが子ノードを親コンポーネントの外部のDOMノードにレンダリングするためです。

上記は、3 種類のノードの削除プロセスです。注目すべきは、unmountHostComponents 関数を実行してサブツリーをトラバースし、各ノードをアンインストールするときに、HostPortal タイプの子ノードに遭遇すると、unmountHostComponents が再度呼び出され、それをターゲット ノードとして使用して、そのノードとそのサブツリーをアンインストールして削除するということです。これは再帰プロセスに相当します。

コミットアンマウント

HostComponent と ClassComponent の削除は両方とも commitUnmount を呼び出し、FunctionComponent もそれを呼び出します。その役割は 3 つのコンポーネントごとに異なります。

  • FunctionComponent 関数コンポーネントで useEffect が呼び出されると、アンインストール時に useEffect の破棄関数が呼び出される必要があります。 (useLayoutEffectの破棄関数はcommitHookEffectListUnmountを呼び出すことで実行されます)
  • ClassComponentクラスコンポーネントはcomponentWillUnmountを呼び出す必要があります
  • HostComponent をアンインストールする ref
  1. 関数 commitUnmount(  
  2. 終了ルート: FiberRoot、  
  3. 電流: ファイバー、  
  4. レンダリング優先度レベル: React優先度レベル、  
  5. ): 空所 {  
  6. onCommitUnmount(現在の値);  
  7. スイッチ (現在のタグ) {  
  8. 関数コンポーネントの場合:  
  9. ケースForwardRef:  
  10. MemoComponentの場合:  
  11. SimpleMemoComponentの場合:  
  12. ケースブロック: {  
  13. const updateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);  
  14. 更新キューが null の場合 
  15. 定数lastEffect = updateQueue.lastEffect ;  
  16. (lastEffect !== null)の場合{  
  17. const firstEffect = lastEffect .next;  
  18. effectfirstEffectします  
  19. する {
  20.   const {destroy, tag} = 効果;  
  21. 破棄が未定義の場合{  
  22. if ((タグ & HookPassive) !== NoHookEffect) {  
  23. // エフェクトをuseEffect破棄関数キューにプッシュします 
  24. enqueuePendingPassiveHookEffectUnmount(現在の値、効果)。  
  25. } それ以外 {  
  26. // try...catch を使用して destroy を呼び出そうとする 
  27. 安全にDestroyを呼び出します(現在の状態、破棄)。  
  28. ...  
  29. }  
  30. }  
  31. 効果effect = effect.next;  
  32. } while (effect !== firstEffect);  
  33. }  
  34. }  
  35. 戻る;  
  36. }  
  37. クラスコンポーネントの場合: {  
  38. 安全にデタッチRef(現在のもの);  
  39. constインスタンス=現在の.stateNode;  
  40. // componentWillUnmount を呼び出す 
  41. if (typeof instance.componentWillUnmount === 'function') {  
  42. 安全にコンポーネントをアンマウントします(現在のインスタンス)。  
  43. }  
  44. 戻る;  
  45. }  
  46. ケースホストコンポーネント: {  
  47. // アンインストール参照 
  48. 安全にデタッチRef(現在のもの);  
  49. 戻る;  
  50. }  
  51. ...  
  52. }  
  53. }

要約する

削除プロセスの重要なポイントを確認しましょう。

  • 削除操作を実行するタイミング
  • 削除対象は誰ですか?
  • 削除する場所

ファイバー ノードに基づいて DOM に対して他の操作を実行する前に、後続の操作のために残されたファイバー ノードが有効であることを確認するために、まずノードを削除する必要があります。削除の対象は、ファイバー ノードとそのサブツリー、およびファイバー ノードに対応する DOM ノードです。 軌跡全体はファイバー ツリーに沿って進み、対象ノードとすべての子ノードをアンロードし、対象ノードに対応する DOM ノード (またはその下の最初のノード) を削除します。ネイティブ DOM タイプのノードの場合、親 DOM ノードから直接削除されます。HostPortal ノードの場合、子ノードは外部 DOM ノードにレンダリングされるため、この DOM ノードから削除されます。上記 3 つのポイントを明確にし、上記の整理プロセスと組み合わせることで、削除操作のコンテキストを徐々に整理することができます。

<<:  ロボット革命が都市のライフスタイルをどう変えるのか

>>:  業界丨2020年のインテリジェントウェーブを理解するには、BaiduとGoogleのAIの足跡から始める

ブログ    

推薦する

「5つの一般的なアルゴリズム」分岐アルゴリズムとアイデアを図解で紹介

[[355166]]この記事はWeChatの公開アカウント「bigsai」から転載したもので、著者は...

...

...

...

初心者のための CNN と Keras のクイックガイド

[[201203]] 1. Keras を使用する理由ディープラーニングが大人気の昨今、サードパーテ...

...

チューリング学習:新世代のロボットは観察するだけで人間を模倣できる

[[187204]]最近、シェフィールド大学自動制御システム工学部のロデリッヒ・グロス博士は次のよう...

...

工場に産業用 IoT テクノロジーを導入する 5 つの理由

モノのインターネット(IoT)はどこにでもあります。実際、ここ数年、スマート製造、サプライ チェーン...

人工知能が火星の新しいクレーターの発見に貢献

人工知能ツールによって特定された、火星の最新のクレーター群の高解像度画像。画像出典: Space.c...

...

...

闇の奥:人工知能の奥にはどんな闇が隠されているのか?

4月13日、TechnologyReviewによると、ロボットが倉庫への特定のルートを取ることを決...

2021年9月のドローン業界の最新動向を3分で振り返る

現在、人工知能や5Gなどの技術の助けを借りて、我が国のドローン開発は急速な成長の軌道に乗っています。...