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リモート接続権限の詳細な説明

推薦する

VMwareのCentosシステムでNavicatがMySQLサーバーに接続できない問題を解決します

ホスト 'xxxx' はこの MySQL サーバーに接続できませんエラー: 1130...

Ubuntu で .sh ファイルを実行するいくつかの方法の違いについて簡単に説明します。

序文特に bash 環境では、スクリプトの実行方法によって結果が異なります。スクリプトを実行する方法...

Vueがsweetalert2プロンプトコンポーネントを統合する際の問題についてお話ししましょう

目次1. プロジェクト統合1. CDNインポート方法: 2. 箱の梱包を確認する3. 迅速な箱詰め4...

CSSで制御可能な点線を実装する方法

序文CSS を使用して点線を生成するのは、フロントエンド開発者にとっては簡単です。一般的に、これを実...

WeChatミニプログラムユーザー認証による携帯電話番号の取得(getPhoneNumber)

序文ミニプログラムには、ユーザーを取得するための非常に便利な API があり、getPhoneNum...

docker-compose でデプロイしたときに MySQL にアクセスできなくなる問題の簡単な分析

Docker-ComposeとはCompose プロジェクトは、以前の fig プロジェクトから派生...

CSS3.0 でネオンボタンアニメーション効果を実装するためのサンプルコード

今日は、CSS 3.0 で実装されたネオン ボタン アニメーション効果を紹介します。効果は次のとおり...

CSS はスクロールバーを非表示にしてコンテンツをスクロールする効果を実現します (3 つの方法)

フロントエンド開発では、スクロールバーを非表示にしながらスクロールをサポートしなければならないという...

MySQL8 ベースの docker-compose デプロイメント プロジェクトの実装

1. まず、次のパスに従って対応するフォルダを作成します。 ローカルのdockerでmysqlを実行...

表のセル間の境界線/区切り線を非表示にする方法

上の境界線のみを表示する <table frame=above>下の境界線のみを表示する...

Linux ドメイン ネーム サービス DNS 設定方法

DNSとはDNS の正式名称は Domain Name System で、ドメイン名解決システムを意...

MySQLデータ移行の概要

目次序文: 1. データ移行について2. 移行計画と留意点要約:序文:日常業務では、テーブル、データ...

Docker プライマリ ネットワーク ポート マッピング構成

ポートマッピングDocker コンテナを起動する前にポート マッピングを行わないと、コンテナ外部のネ...

Vue は Tencent Map を統合して API を実装します (デモ付き)

目次執筆の背景プロジェクトの説明事前準備注記執筆の背景以前のプロジェクトではTencent Maps...

CSS3で実装された天気アイコンのアニメーション効果

成果を達成する 実装コードhtml <div class="wrapper"...