序文フロントエンド開発では、リストのレンダリングが関係する限り、React フレームワークでも Vue フレームワークでも、各リスト項目に一意のキーを使用するように求められたり要求されたりします。多くの開発者は、キーの原理を知らずに、配列インデックスをキー値として直接使用します。この記事では、キーの役割と、キーの属性値としてインデックスを使用しないことが最適な理由について説明します。 キーの役割Vue は仮想 DOM を使用し、diff アルゴリズムに従って古い DOM と新しい DOM を比較して実際の DOM を更新します。キーは仮想 DOM オブジェクトの一意の識別子です。キーは diff アルゴリズムで非常に重要な役割を果たします。 差分アルゴリズムにおけるキーの役割実際、React と Vue の diff アルゴリズムはほぼ同じですが、diff の比較方法はまだかなり異なり、各バージョンの diff もかなり異なります。次に、Vue3.0のdiffアルゴリズムを出発点として、diffアルゴリズムにおけるキーの役割を分析します。 具体的な差分処理は以下のとおりです Vue3.0ではpatchChildrenメソッドにこのようなソースコードがあります パッチフラグ > 0 の場合 パッチフラグとパッチフラグ.KEYED_FRAGMENTの場合{ /* キーが存在する場合は、diffアルゴリズムを使用します */ パッチキー付き子供( ... ) 戻る } そうでない場合 (patchFlag & PatchFlags.UNKEYED_FRAGMENT) { /* キーが存在しない場合は直接パッチを適用します */ パッチUnkeyedChildren( ... ) 戻る } } patchChildren は、キーが存在するかどうかに応じて、実際の diff または直接パッチを実行します。キーが存在しないケースについては詳しく説明しません。 まず、宣言された変数をいくつか見てみましょう。 /* c1 古い vnode c2 新しい vnode */ let i = 0 /* レコードインデックス */ const l2 = c2.length /* 新しい vnode の数*/ let e1 = c1.length - 1 /* 古いvnodeの最後のノードのインデックス*/ let e2 = l2 - 1 /* 新しいノードの最後のノードのインデックス*/ ヘッドノードを同期する最初のステップは、最初から同じ vnode を見つけて、それをパッチすることです。同じノードでない場合は、すぐにループから抜け出します。 //(ab) c //(ab)デ /* 最初から比較して同じノードパッチを見つけ、異なる場合はすぐにジャンプします*/ i <= e1 && i <= e2 の場合 定数 n1 = c1[i] const n2 = (c2[i] = 最適化 ? cloneIfMounted(c2[i]をVNodeとして) : VNodeを正規化する(c2[i]) /* キーと型が等しいかどうかをチェックします*/ (isSameVNodeType(n1, n2)) の場合 { パッチ( ... ) } それ以外 { 壊す } 私は++ } プロセスは次のとおりです。 isSameVNodeTypeは、現在のvnodeタイプがvnodeキーと等しいかどうかを判断するために使用されます。 エクスポート関数 isSameVNodeType(n1: VNode, n2: VNode): boolean { n1.type === n2.type && n1.key === n2.key を返します } 実際、これを見ると、diff アルゴリズムにおけるキーの役割、つまり同じノードであるかどうかを判断することがすでにわかっています。 テールノードを同期する2番目のステップは、前と同じ差分で最後から始まります。 //a (bc) //de (bc) /* 最初のステップがパッチされていない場合は、すぐに後ろから前に向かってパッチを当て始めます。違いが見つかった場合は、すぐにループから抜け出します*/ i <= e1 && i <= e2 の場合 定数 n1 = c1[e1] const n2 = (c2[e2] = 最適化 ? cloneIfMounted(c2[e2]をVNodeとして) : VNodeを正規化する(c2[e2]) (isSameVNodeType(n1, n2)) の場合 { パッチ( ... ) } それ以外 { 壊す } e1-- e2-- } 最初のステップの後、パッチが完全ではないことが判明した場合は、すぐに 2 番目のステップに進み、最後から始めて前方に diff を走査します。同じノードでない場合は、すぐにループから抜け出します。プロセスは次のとおりです。 新しいノードの追加ステップ3: 古いノードがすべてパッチ適用され、新しいノードがパッチ適用されていない場合は、新しいvnodeを作成します。 //(アブ) //(ab) c //i = 2、e1 = 1、e2 = 2 //(アブ) //タクシー) //i = 0、e1 = -1、e2 = 0 /* 新しいノードの数が古いノードの数より多い場合、残りのすべてのノードは新しい vnode として処理されます (つまり、同じ vnode がパッチ適用されたことになります) */ もし(i>e1){ もし(i <= e2){ 定数 nextPos = e2 + 1 const アンカー = nextPos < l2 ? (c2[nextPos] を VNode として).el : parentAnchor i <= e2 の場合 patch( /* 新しいノードを作成する */ ... ) 私は++ } } } プロセスは次のとおりです。 冗長ノードを削除するステップ 4: すべての新しいノードにパッチが適用され、古いノードが残っている場合は、すべての古いノードをアンインストールします。 //i>e2 です //(ab) c //(アブ) //i = 2、e1 = 2、e2 = 1 //a (bc) //(紀元前) //i = 0、e1 = 0、e2 = -1 そうでない場合 (i > e2) { i <= e1 の場合 アンマウント(c1[i], 親コンポーネント, 親サスペンス, true) 私は++ } } プロセスは次のとおりです。 最長増加部分列この時点ではまだ核となるシーンは現れていません。運が良ければここで終わるかもしれませんが、完全に運に頼ることはできません。残りのシナリオは、新しいノードと古いノードの両方に複数の子ノードがあることです。次に、Vue3 がどのようにそれを実行するかを見てみましょう。移動、追加、アンインストールの操作を組み合わせる 要素を移動するたびに、パターンを見つけることができます。要素を移動する回数を最小限に抑えるには、一部の要素を安定させる必要があります。では、安定した状態を維持できる要素のパターンは何でしょうか。 上記の例を見てください: c h d e VS d e i c。比較すると、c を末尾に移動し、h をアンインストールして i を追加するだけでよいことが肉眼でわかります。 d e は変更されないままです。新旧ノードの d e の順序は変更されていないことがわかります。d は e の後にあり、添え字は増加状態にあります。
次のアイデアは、新しいノード シーケンス内の順序が変更されないノードを見つけることができれば、移動する必要がないノードがわかり、ここにないノードのみを挿入すればよいというものです。最終的に提示される順序は新しいノードの順序であり、移動は古いノードの移動にすぎないため、古いノードが最長順序を変更せずに維持する限り、個々のノードを移動することで、その順序との一貫性を保つことができます。そのため、その前に、まずすべてのノードを見つけて、対応するシーケンスを見つけます。最終的に実際に取得したいのは、この配列です: [2, 3, new, 0]。実はこれがdiffの動きの考え方です インデックスを使用しないのはなぜですか?パフォーマンスコストインデックスをキーとして使用すると、各ノードが対応するキーを見つけることができず、一部のノードを再利用できず、すべての新しい vnode を再作成する必要があるため、順次操作が破棄されます。 例: <テンプレート> <div class="hello"> <ul> <li v-for="(item,index) in studentList" :key="index">{{item.name}}</li> <br> <button @click="addStudent">データを追加する</button> </ul> </div> </テンプレート> <スクリプト> エクスポートデフォルト{ 名前: 'HelloWorld', データ() { 戻る { 学生リスト: [ { id: 1, 名前: '张三', 年齢: 18 }, { id: 2、名前: 'Li Si'、年齢: 19 }、 ]、 }; }, 方法:{ 生徒を追加します(){ const studentObj = { id: 3, name: '王五', age: 20 }; this.studentList=[studentObj,...this.studentList] } } } </スクリプト> まず Chorme デバッガーを開き、ダブルクリックして内部のテキストを変更してみましょう。 上記のコードを実行して結果を確認してみましょう。 上記の実行結果から、追加したのは 1 つのデータだけであるのに、3 つのデータすべてを再レンダリングする必要があることがわかります。驚きではありませんか? 明らかに 1 つのデータだけを挿入したのに、なぜ 3 つのデータすべてを再レンダリングする必要があるのでしょうか?私が望んでいるのは、新しく追加されたデータをレンダリングすることだけです。 上記では、diff 比較方法についても説明しました。次に、diff 比較に基づいて図を描いて、比較がどのように行われるかを見てみましょう。 先頭にデータを追加すると、インデックスの順序が中断され、新しいノードのすべてのキーが変更されるため、ページ上のデータが再レンダリングされます。 次に、インデックスを使用する場合と使用しない場合のパフォーマンスを比較するために、1000 個の DOM を生成します。キーの一意性を保証するために、キーとして uuid を使用します。 インデックスをキーとして1回実行してみましょう <テンプレート> <div class="hello"> <ul> <button @click="addStudent">データを追加する</button> <br> <li v-for="(item,index) in studentList" :key="index">{{item.id}}</li> </ul> </div> </テンプレート> <スクリプト> 'uuid/v1' から uuidv1 をインポートします。 エクスポートデフォルト{ 名前: 'HelloWorld', データ() { 戻る { 学生リスト: [{id:uuidv1()}], }; }, 作成された(){ (i = 0; i < 1000; i++ とします) { this.studentList.push({ id: uuidv1(), }); } }, 更新前(){ コンソールで時間を指定します。 }, 更新されました(){ console.timeEnd('for') //for: 75.259033203125 ミリ秒 }, 方法:{ 生徒を追加します(){ 定数studentObj = { id: uuidv1() }; this.studentList=[studentObj,...this.studentList] } } } </スクリプト> IDをキーとして置き換える <テンプレート> <div class="hello"> <ul> <button @click="addStudent">データを追加する</button> <br> <li v-for="(item,index) in studentList" :key="item.id">{{item.id}}</li> </ul> </div> </テンプレート> 更新前(){ コンソールで時間を指定します。 }, 更新されました(){ console.timeEnd('for') //for: 42.200927734375 ミリ秒 }, 上記の比較から、一意の値をキーとして使用するとオーバーヘッドを節約できることがわかります。 データの不整合上記の例では、インデックスをキーとして使用すると、ページの読み込み効率にのみ影響し、少量のデータでは影響が小さいと思われるかもしれません。次のケースでは、インデックスを使用すると予期しない問題が発生する可能性があります。上記のシナリオでも、最初に各テキストコンテンツの後に入力ボックスを追加し、入力ボックスに手動でコンテンツを入力してから、ボタンを使用してクラスメートを前方に追加します。 <テンプレート> <div class="hello"> <ul> <li v-for="(item,index) in studentList" :key="index">{{item.name}'',input /></li> <br> <button @click="addStudent">データを追加する</button> </ul> </div> </テンプレート> <スクリプト> エクスポートデフォルト{ 名前: 'HelloWorld', データ() { 戻る { 学生リスト: [ { id: 1, 名前: '张三', 年齢: 18 }, { id: 2、名前: 'Li Si'、年齢: 19 }、 ]、 }; }, 方法:{ 生徒を追加します(){ const studentObj = { id: 3, name: '王五', age: 20 }; this.studentList=[studentObj,...this.studentList] } } } </スクリプト> 入力にいくつかの値を入力し、クラスメートを追加して効果を確認してみましょう。 この時点で、追加前に入力したデータが間違っていることがわかります。追加した後、張三の情報が王武の入力ボックスに残りますが、これは明らかに私たちが望んでいる結果ではありません。 上の比較から、インデックスをキーにしているため、比較するとテキスト値が変化していることがわかりますが、下方向に比較し続けると、Input DOM ノードは以前と同じであることがわかります。そのため、再利用されます。ただし、入力ボックスが入力値を保持することは予想外であり、このとき、入力値が間違って配置されます。 解決インデックスを使用すると場合によっては非常に悪い影響が出る可能性があることがわかっているので、開発中にこの問題をどのように解決すればよいでしょうか?実際、キーが一意で変更されていない限り、開発では次の 3 つの状況がより頻繁に使用されます。
a=Symbol('test') とします。 b = シンボル('テスト')とします。 console.log(a===b) //偽 キーとして uuid を使用できます。uuid は Universally Unique Identifier の略です。これは、特定の範囲 (特定の名前空間から世界まで) 内で一意である、機械によって生成された識別子です。 上記の最初の解決策をキーとして使用し、図に示すように、上記の状況をもう一度見てみます。同じキーを持つノードは再利用されます。これが diff アルゴリズムの実際の効果です。 要約する
これで、Vue でインデックスをキーとして使用することが推奨されない理由に関するこの記事は終了です。Vue のインデックスをキーとして使用することに関する関連コンテンツの詳細については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM を応援していただければ幸いです。 以下もご興味があるかもしれません:
|
<<: CSS パフォーマンスの最適化 - will-change の使用方法の詳細な説明
>>: Mysql クエリの結果セットを JSON データに変換するサンプル コード
データベースでcreate tableステートメントを実行する テーブル `sys_acl` を作成...
質問があります。Dreamweaver で、3 行 1 列のログイン フォーム (ログイン、登録、パ...
目次1. トリガーとは何ですか? 2. トリガーを作成するトリガーを作成するための構文は次のとおりで...
この記事では、Linux ファイル管理コマンドについて例を挙げて説明します。ご参考までに、詳細は以下...
背景記事を始める前に、賽博朋克とは何か、賽博朋克2077とは何かを簡単に理解しましょう。サイバーパン...
1. プロキシサーバーとは何ですか?プロキシ サーバーは、クライアントが要求を送信すると、それを直接...
JavaScriptでよく使われるいくつかの文字列メソッド文字列は読み取り専用データです。よく使用...
Alibaba Cloud ServerがFTPに接続できないFileZilla 425 データ接続...
はじめに: プロジェクトを開発するために、サーバーに MySql データベース サーバーを展開し、ロ...
1. SSHリモート管理SSH はセキュア チャネル プロトコルであり、主にリモート ログイン、リモ...
最近、docker を学習していたときに、docker コンテナ内のネットワーク状態を照会するために...
この記事では、パズルゲームを実装するためのjsの具体的なコードを参考までに共有します。具体的な内容は...
目次まず、スクロール バーのスタイルを変更するには、疑似要素-webkit-scrollbarを使用...
ウェブサイトのモバイル版には、少なくともいくつかの基本機能が必要です。 1. ページの適用性の問題:...
序文Linux グループは、Linux でユーザー アカウントを管理するために使用される組織単位です...