Vueスロットの実装原理についての簡単な説明

Vueスロットの実装原理についての簡単な説明

1. サンプルコード

<!-- サブコンポーネント comA -->
<テンプレート>
  <div class='デモ'>
    <スロット><スロット>
    <スロット名='テスト'></スロット>
    <スロット名='scopedSlots' テスト='デモ'></スロット>
  </div>
</テンプレート>
<!-- 親コンポーネント -->
<comA>
  <span>これがデフォルトのスロットです</span>
  <template slot='test'>これは名前付きスロットです</template>
  <template slot='scopedSlots' slot-scope='scope'>これはスコープ付きスロットです (旧バージョン) {{scope.test}}</template>
  <template v-slot:scopedSlots='scopeProps' slot-scope='scope'>これはスコープ付きスロットです (新バージョン) {{scopeProps.test}}</template>
</comA>

2. 現象を通して本質を見る

スロットの役割は、コンテンツの配信を実現することです。コンテンツの配信を実現するには、次の 2 つの条件が必要です。

  • プレースホルダー
  • コンテンツを配信する

コンポーネント内で定義されたslotタグはプレースホルダーとして理解でき、親コンポーネント内のスロット コンテンツが配信されるコンテンツになります。スロット処理の本質は、指定されたコンテンツを指定された場所に配置することです。さっそく、この記事から次のことを学びましょう。

  • スロットの実装原理
  • renderメソッドでスロットを使用する方法

3. 実施原則

vueコンポーネントのインスタンス化の順序は次のとおりです: 親コンポーネントの状態の初期化 ( datacomputedwatch ...) --> テンプレートのコンパイル --> renderメソッドの生成 --> レンダリングwatcherのインスタンス化 --> renderメソッドの呼び出し、 VNodeの生成 --> patch VNode 、実際のDOMへの変換 --> 子コンポーネントのインスタンス化 --> ... 同じプロセスの繰り返し --> 子コンポーネントによって生成された実際のDOMが、親コンポーネントによって生成された実際のDOMにマウントされ、ページにマウントされます --> 古いノードを削除します

上記のプロセスから、次のことが推測できます。

1. 親コンポーネントテンプレートは子コンポーネントの前に解析されるため、親コンポーネントは最初にスロットテンプレートのコンテンツを取得します。

2. サブコンポーネントテンプレートは後で解析されるので、サブコンポーネントがrenderメソッドを呼び出してVNodeを生成するときに、何らかの方法でスロットのVNodeノードを取得できます。

3. スコープスロットはサブコンポーネント内の変数を取得できるため、スコープスロットのVNode生成は動的であり、つまり、サブコンポーネントのscopeリアルタイムで渡す必要がある。

スロット処理フェーズ全体は、おおまかに次の 3 つのステップに分かれています。

  • コンパイル
  • レンダリングテンプレートを生成する
  • VNode を生成する

以下のコードを例に、スロットの動作プロセスを簡単に説明します。

<div id='アプリ'>
  <テスト>
    <テンプレートスロット="hello">
      123
    </テンプレート>
  </テスト>
</div>
<スクリプト>
  新しいVue({
    el: '#app',
    コンポーネント:
      テスト: {
        テンプレート: '<h1>' +
          '<スロット名="hello"></スロット>' +
          '</h1>'
      }
    }
  })
</スクリプト>

4. 親コンポーネントのコンパイルフェーズ

コンパイルでは、テンプレート ファイルをAST構文ツリーに解析し、スロットtemplateを次のデータ構造に解析します。

{
  タグ: 'テスト',
  scopedSlots: { // スコープスロット // slotName: ASTNode,
    // ...
  }
  子供たち: [
    {
      タグ: 'テンプレート',
      // ...
      親: parentASTNode、
      children: [ childASTNode ], // スロットコンテンツの子ノード、つまりテキストノード 123
      slotScope: undefined、// スコープ スロット バインディング値 slotTarget: "\"hello\"", // 名前付きスロット名 slotTargetDynamic: false // 動的にバインドされたスロットかどうか // ...
    }
  ]
}

5. 親コンポーネントがレンダリングメソッドを生成する

AST構文ツリーに従って、レンダリングメソッド文字列を解析して生成します。親コンポーネントによって生成される最終結果は次のとおりです。この構造は、 renderメソッドを直接記述することと一致しています。本質はVNodeを生成することですが、 _cまたはhthis.$createElementの省略形です。

(これ){
  _c('div',{attrs:{"id":"app"}}, を返します。
  [_c('テスト',
    [
      _c('テンプレート',{スロット:"hello"},[_v("\n 123\n ")])],2)
    ]、
  1)
}

6. 親コンポーネントがVNodeを生成する

renderメソッドを呼び出してVNodeを生成します。VNode VNode具体的な形式は次のとおりです。

{
  タグ: 'div',
  親: 未定義、
  data: { // VNode 構成項目を保存 attrs: { id: '#app' }
  },
  context: componentContext, // コンポーネントスコープ elm: undefined, // 実際のDOM要素 children: [
    {
      タグ: 'vue-component-1-test',
      children: undefined, // コンポーネントはページの最小単位であり、スロットコンテンツは子コンポーネントで解析されます parent: undefined,
      componentOptions: { // コンポーネント構成項目 Ctor: VueComponentCtor, // コンポーネント構築方法 data: {
          フック: {
            init: fn, //メソッド呼び出しコンポーネントをインスタンス化します insert: fn,
            プレパッチ: fn、
            破壊する: fn
          },
          scopedSlots: { //スコープスロット構成項目。スコープスロットVNodeを生成するために使用される
            スロット名: slotFn
          }
        },
        children: [ // コンポーネントスロットノードタグ: 'template',
          propsData: undefined, // propsパラメータ listeners: undefined,
          データ: {
            スロット: 'hello'
          },
          子: [ VNode ],
          親: 未定義、
          context: componentContext // 親コンポーネントのスコープ // ...
        ] 
      }
    }
  ]、
  // ...
}

vueでは、コンポーネントはページ構造の基本単位です。上記のVNodeから、 VNodeページ階層はtestコンポーネントで終了し、 testコンポーネントのchildren処理はサブコンポーネントの初期化プロセス中に処理されることもわかります。サブコンポーネント構築メソッドのアセンブリと属性のマージは vue-dev\src\core\vdom\create-component.js createComponentメソッドにあり、コンポーネントのインスタンス化呼び出しエントリは vue-dev\src\core\vdom\patch.js createComponentメソッドにあります。

7. サブコンポーネント状態の初期化

サブコンポーネントをインスタンス化すると、サブコンポーネント スロット ノードは、 initRender -> resolveSlotsメソッドで、 vm.$slots = {slotName: [VNode]}の形式でコンポーネント スコープvmにマウントされます。

8. サブコンポーネントのコンパイルフェーズ

コンパイル フェーズでは、サブコンポーネントはslotノードを次のAST構造にコンパイルします。

{
  タグ: 'h1',
  親: 未定義、
  子供たち: [
    {
      タグ: 'スロット',
      スロット名: "\"hello\"",
      // ...
    }
  ]、
  // ...
}

9. サブコンポーネント生成レンダリング方法

生成されたレンダリング メソッドは次のとおりです。_t _t renderSlotメソッドの略語です。renderSlot renderSlotから、スロット コンテンツとスロット ポイントを直感的にリンクできます。

// レンダリングメソッド with(this){
  _c('h1',[ _t("hello") ], 2) を返します
}
// ソースコードパス: vue-dev\src\core\instance\render-helpers\render-slot.js
エクスポート関数renderSlot(
  名前: 文字列、
  フォールバック: ?Array<VNode>,
  小道具: ?オブジェクト、
  バインドオブジェクト: ?オブジェクト
): ?配列<VNode> {
  const scopedSlotFn = this.$scopedSlots[名前]
  ノードを
  if (scopedSlotFn) { // スコープ付きスロット
    小道具 = 小道具 || {}
    if (bindObject) {
      process.env.NODE_ENV !== 'production' && !isObject(bindObject) の場合 {
        警告(
          '引数なしのスロット v-bind はオブジェクトを期待します'、
          これ
        )
      }
      props = extend(extend({}, bindObject), props)
    }
    //スコープ スロット、スロット VNode を取得
    ノード = scopedSlotFn(props) || フォールバック
  } それ以外 {
    // スロットの通常スロット VNode を取得します
    nodes = this.$slots[name] || フォールバック
  }

  const ターゲット = props && props.slot
  if (ターゲット) {
    this.$createElement('template', { slot: target }, nodes) を返します。
  } それ以外 {
    ノードを返す
  }
}

スコープ付きスロットと名前付きスロットの違い

<!-- デモ -->
<div id='アプリ'>
  <テスト>
      <テンプレート スロット="hello" スロット スコープ='スコープ'>
        {{scope.hello}}
      </テンプレート>
  </テスト>
</div>
<スクリプト>
    var vm = 新しい Vue({
        el: '#app',
        コンポーネント:
            テスト: {
                データ () {
                    戻る {
                        こんにちは: '123'
                    }
                },
                テンプレート: '<h1>' +
                    '<スロット名="hello" :hello="hello"></スロット>' +
                  '</h1>'
            }
        }
    })

</スクリプト>

スコープ スロットと通常のスロットの主な違いは、スロット コンテンツがサブコンポーネント スコープ変数を取得できることです。サブコンポーネント変数を挿入する必要があるため、スコープ スロットは名前付きスロットとは次の点で異なります。

レンダリング メソッドをアセンブルすると、スコープ スロットは注入スコープを含むメソッドを生成します。 VNode生成するためのcreateElementと比較すると、注入スコープ メソッド ラッピングの追加レイヤーがあり、子コンポーネントがVNodeを生成するときにスロットVNodeスコープ スロットが生成され、親コンポーネントがVNodeを作成するときに名前付きスロットが生成されることを決定します。 _u 、ノード構成項目を{scopedSlots: {slotName: fn}}の形式に変換するresolveScopedSlotsです。

(これ){
        _c('div', { を返す
            属性: {
                「id」: 「アプリ」
            }
        }, [_c('テスト', {
            スコープスロット: _u([{
                キー: "hello",
                fn: 関数(スコープ) {
                    [_v("\n " + _s(scope.hello) + "\n ")] を返します
                }
            }])
        })], 1)
    }

サブコンポーネントが初期化されると、名前付きスロットノードが処理され、コンポーネント$slotsにマウントされ、スコープ付きスロットがrenderSlotで直接呼び出されます。

それ以外は、プロセスはほぼ同じです。スロットの仕組みは理解するのが難しくありませんが、鍵となるのはテンプレートの解析とレンダリング関数の生成という 2 つのステップであり、これらはより複雑でプロセスが長く、理解するのがより困難です。

10. 使用上のヒント

以上の分析により、スロット処理フローを大まかに理解することができます。ほとんどの作業はテンプレートを使用してvueコードを記述することで行われますが、テンプレートには特定の制限がある場合があり、 renderメソッドを使用してvueのコンポーネント抽象化機能を増幅する必要があります。次に、 renderメソッドでスロットを次のように使用します。

10.1. 名前付きスロット

スロット処理は、一般的に次の 2 つの部分に分かれています。

  • 親コンポーネント: 親コンポーネントは、テンプレートからコンパイルされたレンダリングメソッドとして記述するだけでよく、つまりslot名を指定します。
  • サブコンポーネント: サブコンポーネントは初期化フェーズ中に親コンポーネントによって生成されたVNode直接取得するため、サブコンポーネントはslotタグを親コンポーネントによって生成されたVNodeに置き換えるだけで済みます。サブコンポーネントは、初期化時に名前付きスロットをコンポーネントの$slots属性にマウントします。
<div id='アプリ'>
<!-- <テスト>-->
<!-- <テンプレートスロット="hello">-->
<!-- 123-->
<!-- </テンプレート>-->
<!-- </テスト>-->
</div>
<スクリプト>
  新しいVue({
    // el: '#app',
    レンダリング (要素を作成) {
      createElement('test', [ を返します。
        要素を作成します('h3', {
          スロット: 'hello',
          domProps: {
            内部テキスト: '123'
          }
        })
      ])
    },
    コンポーネント:
      テスト: {
        レンダリング(要素を作成) {
          createElement('h1', [ this.$slots.hello ] を返します。)
        }
        // テンプレート: '<h1>' +
        // '<スロット名="hello"></スロット>' +
        // '</h1>'
      }
    }
  }).$mount('#app')
</スクリプト>

10.2. スコープ付きスロット

スコープ付きスロットはより柔軟に使用でき、サブコンポーネントの状態を挿入できます。スコープ付きスロット + renderメソッドは、セカンダリ コンポーネントのカプセル化に非常に役立ちます。たとえば、 JSONデータに基づいてElementUI tableコンポーネントをカプセル化する場合、スコープ スロットは非常に便利です。

<div id='アプリ'>
<!-- <テスト>-->
<!-- <span slot="hello" slot-scope='scope'>-->
<!-- {{scope.hello}}-->
<!-- </span>-->
<!-- </テスト>-->
</div>
<スクリプト>
  新しいVue({
    // el: '#app',
    レンダリング (要素を作成) {
      createElement('test', を返す。
        スコープスロット:{
          hello: scope => { // 親コンポーネントのレンダリングメソッドでは、最終的に変換されたスコープスロットメソッドはこの記述方法と一致しています return createElement('span', {
              domProps: {
                内部テキスト: scope.hello
              }
            })
          }
        }
      })
    },
    コンポーネント:
      テスト: {
        データ () {
          戻る {
            こんにちは: '123'
          }
        },
        レンダリング (要素を作成) {
          // スコープスロットの親コンポーネントは関数を渡します。この関数は手動で呼び出す必要があり、VNode を生成します。
          slotVnode = this.$scopedSlots.hello({ hello: this.hello }) とします。
          createElement('h1', [ slotVnode ]) を返します
        }
        // テンプレート: '<h1>' +
        // '<スロット名="hello" :hello="hello"></スロット>' +
        // '</h1>'
      }
    }
  }).$mount('#app')

</スクリプト>

上記は、Vue スロットの実装原理の詳細な内容についての簡単な説明です。Vue スロットの詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • Vue 学習ノート スロットの使用例分析
  • Vue 学習ノート: スロット スロットの基本的な使用例の分析
  • Vue 学習ノート スコープ スロットの例の分析
  • Vueの$slotはスロットのノードインスタンスを取得します
  • Reactコンテキストを使用してvueスロット関数を実装する
  • Vue スロットとスコープ付きスロットの理解に関する簡単な分析
  • Vue の匿名スロットとスコープ付きスロットのマージと上書きの動作
  • Vue の匿名スロット、名前付きスロット、スコープ付きスロットの使い方の詳細な説明
  • Vue でのスロットとクラスタースロットの使用に関する詳細な説明
  • Vue スロットの理解と使用

<<:  写真とテキストによる MySQL 8.0.11 インストール チュートリアル

>>:  MySQLリモート接続権限の詳細な説明

推薦する

あまり多くのコードを書かずに、ハイパーリンクを使ってシンプルで美しいカスタムチェックボックスを実装できます。

今日ふと、HTML でチェックボックスのスタイルを変更できる範囲が限られていることと、チェックボック...

MySQL データベースのバックアップとリカバリの実装コード

データベースのバックアップ #文法: # mysqldump -h server-u usernam...

MySQL でテーブル データを削除した後もディスク領域がまだ占有されているのはなぜですか?

目次1.MySQLデータ構造2. テーブルファイルのサイズは変更されておらず、MySQLの設計に関連...

CSS3 レーダースキャンマップのサンプルコード

CSS3 を使用して、クールなレーダースキャン画像を実現します。 コード上で直接: // インデック...

RGBA の「a」は何を意味するのでしょうか? CSS RGBA カラー ガイド

RGBAは色の値と透明度を設定できるCSSカラーです以下は、rgba() を使用して白色を 50% ...

MySQL最適化ツール(推奨)

序文今日 GitHub を閲覧していたところ、SQL を最適化および書き換えるための sora とい...

Vue 初心者ガイド: 環境の構築と開始方法

目次初期ビューVue開発環境の構築Vueインスタンスの作成Vue テンプレート構文Vue データバイ...

Navicat for MySQL 15 登録とアクティベーションの詳細なチュートリアル

1. Navicat for MySQL 15をダウンロードするhttps://www.navica...

Nginx設定の原理と実装プロセスの詳細な説明https

Linuxユーティリティcertbotを使用してhttps証明書を生成するこのツールは Let&#...

MySQL における制限関数と合計関数の混在使用の問題の詳細な説明

序文今日、注文データを同期した後、同僚は、合計注文金額とデータソースの合計金額に差があったため、LI...

GolangでMySQLデータベースを操作するための実装コード

序文Golang は、SQL データベースにアクセスするための database/sql パッケージ...

テキストの円形スクロールアニメーションを実装するミニプログラム

この記事では、参考までに、テキストループスクロールを実現するアプレットの具体的なコードを例を挙げて紹...

Mysql マスタースレーブレプリケーションの注意事項の説明

1. マスター'x@xxxx:x'への接続エラー- 再試行時間: 60 再試行回数:...

JavaScriptがDOMツリーの構築にどのように影響するかについて詳しく説明します。

目次ドキュメント オブジェクト モデル (DOM) DOM と JavaScript DOMツリーの...

入力と画像を揃えるためにvertical-alignを使用します

input と img を同じ行に配置すると、img タグが常に input より 1 つ上になり、...