Vue3 の動的コンポーネントはどのように機能しますか?

Vue3 の動的コンポーネントはどのように機能しますか?

この記事では、Abaoge が Vue 3 の組み込みコンポーネントであるコンポーネントを紹介します。このコンポーネントは、「メタ コンポーネント」を動的コンポーネントとしてレンダリングするために使用されます。動的コンポーネントについてあまり詳しくなくても大丈夫です。この記事では、アバオ兄弟が具体的な例を通して動的コンポーネントの応用を紹介します。動的コンポーネントの内部構造とコンポーネント登録の間には一定の関連があるため、動的コンポーネントの内部原理を誰もがよりよく理解できるように、Abao はまずコンポーネント登録の関連知識を紹介します。

1. コンポーネントの登録

1.1 グローバル登録

Vue 3.0 では、アプリ オブジェクトのコンポーネント メソッドを使用して、グローバル コンポーネントを簡単に登録または取得できます。コンポーネント メソッドは、次の 2 つのパラメータをサポートします。

  • name: コンポーネント名;
  • コンポーネント: コンポーネント定義オブジェクト。

次に、簡単な例を見てみましょう。

<div id="アプリ">
 <コンポーネント-a></コンポーネント-a>
 <コンポーネント-b></コンポーネント-b>
 <コンポーネント-c></コンポーネント-c>
</div>
<スクリプト>
 const {createApp} = Vue
 const app = createApp({}); // ①
 app.component('component-a', { // ②
  テンプレート: "<p>私はコンポーネント A です</p>"
 });
 app.component('コンポーネントb', {
  テンプレート: "<p>私はコンポーネント B です</p>"
 });
 app.component('コンポーネント-c', {
  テンプレート: "<p>私はコンポーネント C です</p>"
 });
 app.mount('#app') // ③
</スクリプト>

上記のコードでは、app.component メソッドを通じて 3 つのコンポーネントを登録しました。これらのコンポーネントはグローバルに登録されます。つまり、登録後は、新しく作成されたコンポーネント インスタンスのテンプレート内で使用できるようになります。この例のコードは比較的単純で、主に App オブジェクトの作成、グローバル コンポーネントの登録、アプリケーションのマウントという 3 つのステップで構成されています。 App オブジェクトの作成の詳細については、以降の記事で別途紹介します。以下では、他の 2 つのステップの分析に焦点を当てます。まず、グローバル コンポーネントを登録するプロセスを分析しましょう。

1.2 グローバルコンポーネントの登録プロセス

上記の例では、アプリ オブジェクトのコンポーネント メソッドを使用してグローバル コンポーネントを登録します。

app.component('コンポーネント-a', {
 テンプレート: "<p>私はコンポーネント A です</p>"
});

もちろん、グローバル コンポーネントを登録するだけでなく、コンポーネントは components オプションも受け入れるため、ローカル コンポーネントも登録できます。

const app = Vue.createApp({
 コンポーネント:
 'コンポーネント-a': コンポーネントA、
 'コンポーネント-b': コンポーネントB
 }
})

ローカルに登録されたコンポーネントは、その子コンポーネントでは使用できないことに注意してください。次に、グローバル コンポーネントの登録プロセスを続行します。前の例では、runtime-core/src/apiCreateApp.ts ファイルで定義されている app.component メソッドを使用しました。

エクスポート関数createAppAPI<HostElement>(
 レンダリング: RootRenderFunction、
 水和物?: RootHydrateFunction
): CreateAppFunction<ホスト要素> {
 関数createApp(rootComponent, rootProps = null)を返す{
 定数コンテキスト = createAppContext()
 const インストール済みプラグイン = 新しい Set()
 isMounted = false とします

 const app: App = (context.app = {
  // 一部省略 code_context: context,

  // グローバルコンポーネントを登録または取得するcomponent(name: string, component?: Component): any {
  __DEV__ の場合 {
   コンポーネント名を検証します(名前、context.config)
  }
  if (!component) { // 名前に対応するコンポーネントを取得する return context.components[name]
  }
  if (__DEV__ && context.components[name]) { // 重複登録プロンプト warn(`コンポーネント "${name}" は既に対象アプリに登録されています。`)
  }
  context.components[name] = component // グローバルコンポーネントを登録する appを返す
  },
 })

 返品アプリ
 }
}

すべてのコンポーネントが正常に登録されると、次の図に示すように、コンテキスト オブジェクトの components プロパティに保存されます。

createAppContext 関数は、runtime-core/src/apiCreateApp.ts ファイルで定義されています。

// パッケージ/ランタイムコア/src/apiCreateApp.ts
エクスポート関数createAppContext(): AppContext {
 戻る {
 app: null は任意、
 config: { // アプリケーション構成オブジェクト isNativeTag: NO,
  パフォーマンス: false、
  グローバルプロパティ: {},
  オプションマージ戦略: {},
  isCustomElement: いいえ、
  errorHandler: 未定義、
  warnHandler: 未定義
 },
 mixins: [], // アプリケーションにミックスインされたコンポーネントを保存します components: {}, // グローバルコンポーネントの情報を保存します directives: {}, // グローバル命令の情報を保存します provided: Object.create(null)
 }
}

app.component メソッドを分析した後、コンポーネントの登録プロセスは非常に簡単だと思いませんか?では、登録されたコンポーネントはいつ使用されるのでしょうか?この質問に答えるには、別のステップであるアプリケーションのマウントを分析する必要があります。

1.3 アプリケーションのマウントプロセス

アプリケーションのマウント プロセスをより直感的に理解するために、Abaoge は Chrome 開発者ツールのパフォーマンス タブ バーを使用して、アプリケーションのマウントの主なプロセスを記録しました。

上の図では、コンポーネント関連の関数であるresolveComponentが見つかりました。明らかに、この関数はコンポーネントを解析するために使用され、レンダリング メソッドで呼び出されます。ソース コードには、関数の定義が見つかりました。

// パッケージ/runtime-core/src/helpers/resolveAssets.ts
const COMPONENTS = 'コンポーネント'

エクスポート関数resolveComponent(名前: 文字列): ConcreteComponent | 文字列 {
 戻りresolveAsset(COMPONENTS, name) || name
}

上記のコードから、resolveComponent 関数内で、resolveAsset 関数が引き続き呼び出され、特定の解決操作が実行されることがわかります。 resolveAsset 関数の特定の実装を分析する前に、resolveComponent 関数内にブレークポイントを追加して、render メソッドを確認してみましょう。

上の図では、_resolveComponent("component-a") などのコンポーネントを解決する操作が表示されています。すでにご存知のとおり、resolveAsset 関数は、resolveComponent 関数内で呼び出されます。この関数の具体的な実装は次のとおりです。

// パッケージ/runtime-core/src/helpers/resolveAssets.ts
関数resolveAsset(
 タイプ: typeof COMPONENTS | typeof DIRECTIVES、
 名前: 文字列、
 警告がない = true
){
 const インスタンス = 現在のレンダリングインスタンス || 現在のインスタンス
 if (インスタンス) {
 const コンポーネント = インスタンス.type
 // 処理ロジックのほとんどを省略 const res =
  // ローカル登録 // まず、mixin または extends を持つコンポーネントの instance[type] をチェックします。
  解決(instance[type] || (Component as ComponentOptions)[type], name) ||
  // グローバル登録resolve(instance.appContext[type], name)
 戻り値
 } そうでない場合 (__DEV__) {
 警告(
  `${capitalize(type.slice(0, -1))}を解決します` +
  `render() または setup() でのみ使用できます。`
 )
 }
}

コンポーネントを登録するときにグローバル登録メソッドが使用されるため、解決プロセスでは、resolve(instance.appContext[type], name) ステートメントが実行されます。ここで、resolve メソッドは次のように定義されます。

// パッケージ/runtime-core/src/helpers/resolveAssets.ts
関数resolve(レジストリ: Record<文字列、任意> | 未定義、名前: 文字列) {
 戻る (
 レジストリ &&
 (レジストリ[名前] ||
  レジストリ[camelize(名前)] ||
  レジストリ[大文字にする(キャメル文字にする(名前))])
 )
}

上記の処理フローを分析した後、グローバルに登録されたコンポーネントを解析すると、resolve 関数を通じてアプリケーション コンテキスト オブジェクトから登録されたコンポーネント オブジェクトが取得されます。

(関数匿名() {
 定数_Vue = Vue

 関数render(_ctx, _cache)を返す{
  (_ctx) で{
   const {resolveComponent: _resolveComponent、createVNode: _createVNode、 
   フラグメント: _Fragment、オープンブロック: _openBlock、作成ブロック: _createBlock} = _Vue

   const _component_component_a = _resolveComponent("コンポーネントa")
   const _component_component_b = _resolveComponent("コンポーネントb")
   const _component_component_c = _resolveComponent("コンポーネントc")

   戻り値 (_openBlock(),
   _createBlock(_Fragment, null, [
    _createVNode(_component_component_a)、 
    _createVNode(_component_component_b)、 
    _createVNode(_component_component_c)], 64))
  }
 }
})

コンポーネントを取得した後、_createVNode 関数を通じて VNode ノードが作成されます。ただし、VNode が実際の DOM 要素にどのようにレンダリングされるかについては詳しく説明しません。この内容については、後で別の記事で紹介する予定です。次に、動的コンポーネントの関連コンテンツを紹介します。

2. 動的コンポーネント

Vue 3 では、コンポーネント組み込みコンポーネントが提供されており、「メタコンポーネント」を動的コンポーネントとしてレンダリングできます。 is の値に応じて、どのコンポーネントがレンダリングされるかが決まります。の値が文字列の場合、HTML タグ名またはコンポーネント名のいずれかになります。対応する使用例は次のとおりです。

<!-- 動的コンポーネントは、vm インスタンスの `componentId` プロパティによって制御されます -->
<コンポーネント:is="コンポーネントID"></コンポーネント>

<!-- 登録されたコンポーネントや props によって渡されたコンポーネントもレンダリングできます -->
<コンポーネント :is="$options.components.child"></コンポーネント>

<!-- 文字列を介してコンポーネントを参照できます -->
<component :is="条件 ? 'FooComponent' : 'BarComponent'"></component>

<!-- ネイティブ HTML 要素をレンダリングするために使用できます -->
<component :is="href ? 'a' : 'span'"></component>

2.1 バインディング文字列型

コンポーネントの組み込みコンポーネントを紹介した後、簡単な例を見てみましょう。

<div id="アプリ">
 <ボタン
  v-for="タブ内のタブ"
  :key="タブ"
  @click="currentTab = 'tab-' + tab.toLowerCase()">
  {{タブ}}
 </ボタン>
 <コンポーネント :is="現在のタブ"></コンポーネント>
</div>
<スクリプト>
 const {createApp} = Vue
 const tabs = ['ホーム', 'マイ']
 const app = createApp({
  データ() {
  戻る {
   タブ、
   現在のタブ: 'tab-' + tabs[0].toLowerCase()
  }
  },
 });
 app.component('タブホーム', {
  テンプレート: `<div style="border: 1px solid;">ホーム コンポーネント</div>`
 })
 app.component('タブ-my', {
  テンプレート: `<div style="border: 1px solid;">私のコンポーネント</div>`
 })
 app.mount('#app')
</スクリプト>

上記のコードでは、app.component メソッドを通じて、tab-home と tab-my の 2 つのコンポーネントをグローバルに登録しました。さらに、テンプレートでは、コンポーネント組み込みコンポーネントを使用します。このコンポーネントの is プロパティは、文字列型であるデータ オブジェクトの currentTab プロパティにバインドされています。ユーザーが Tab ボタンをクリックすると、currentTab の値が動的に更新され、コンポーネントを動的に切り替える機能が実現されます。上記の例が正常に実行された結果を次の図に示します。

これを読んで、コンポーネント組み込みコンポーネントは魔法のようだと思いましたか? 興味があれば、引き続きアバオ兄弟をフォローして、その背後にある秘密を解き明かしてください。次に、Vue 3 テンプレート エクスプローラー オンライン ツールを使用して、<component :is="currentTab"></component> テンプレートのコンパイルの結果を確認します。

定数_Vue = Vue

関数 render(_ctx, _cache, $props, $setup, $data, $options) を返します {
 (_ctx) で{
 const { 解決動的コンポーネント: _resolveDynamicComponent、 オープンブロック: _openBlock、 
  ブロックの作成: _createBlock } = _Vue
 戻り値 (_openBlock()、_createBlock(_resolveDynamicComponent(currentTab)))
 }
}

生成されたレンダリング関数を観察すると、resolveDynamicComponent 関数が見つかりました。関数の名前から、動的コンポーネントを解決するために使用されることがわかります。これは、runtime-core/src/helpers/resolveAssets.ts ファイルで定義されています。具体的な実装は次のとおりです。

// パッケージ/runtime-core/src/helpers/resolveAssets.ts
エクスポート関数resolveDynamicComponent(コンポーネント: 不明): VNodeTypes {
 if (isString(コンポーネント)) {
 戻りresolveAsset(COMPONENTS, コンポーネント, false) || コンポーネント
 } それ以外 {
 // 無効な型は createVNode にフォールスルーし、警告が発生します
 (コンポーネント || NULL_DYNAMIC_COMPONENT) を任意の値として返す
 }
}

resolveDynamicComponent 関数内で、コンポーネント パラメータが文字列型の場合、先に紹介した resolveAsset メソッドが呼び出され、コンポーネントが解決されます。

// パッケージ/runtime-core/src/helpers/resolveAssets.ts
関数resolveAsset(
 タイプ: typeof COMPONENTS | typeof DIRECTIVES、
 名前: 文字列、
 警告がない = true
){
 const インスタンス = 現在のレンダリングインスタンス || 現在のインスタンス
 if (インスタンス) {
 const コンポーネント = インスタンス.type
 // 処理ロジックのほとんどを省略 const res =
  // ローカル登録 // まず、mixin または extends を持つコンポーネントの instance[type] をチェックします。
  解決(instance[type] || (Component as ComponentOptions)[type], name) ||
  // グローバル登録resolve(instance.appContext[type], name)
 戻り値
 }
}

前の例では、コンポーネントはグローバルに登録されているため、解決プロセス中に app.context コンテキスト オブジェクトの components プロパティから対応するコンポーネントが取得されます。 currentTab が変更されると、resolveAsset 関数は別のコンポーネントを返すため、動的コンポーネントの機能が実現されます。また、resolveAsset 関数が対応するコンポーネントを取得できない場合は、現在のコンポーネント パラメータの値を返します。たとえば、resolveDynamicComponent('div') は文字列 'div' を返します。

// パッケージ/runtime-core/src/helpers/resolveAssets.ts
エクスポート const NULL_DYNAMIC_COMPONENT = シンボル()

エクスポート関数resolveDynamicComponent(コンポーネント: 不明): VNodeTypes {
 if (isString(コンポーネント)) {
 戻りresolveAsset(COMPONENTS, コンポーネント, false) || コンポーネント
 } それ以外 {
 (コンポーネント || NULL_DYNAMIC_COMPONENT) を任意の値として返す
 }
}

注意深い友人は、resolveDynamicComponent 関数内で、コンポーネント パラメータが文字列型でない場合、ステートメント コンポーネント || NULL_DYNAMIC_COMPONENT の実行結果が返され、NULL_DYNAMIC_COMPONENT の値が Symbol オブジェクトになることに気付いたかもしれません。

2.2 バインディングオブジェクトタイプ

上記の内容を理解した後、以前の動的 Tab 関数を再実装してみましょう。

<div id="アプリ">
 <ボタン
  v-for="タブ内のタブ"
  :key="タブ"
  @click="現在のタブ = タブ">
  {{タブ名}}
 </ボタン>
 <コンポーネント :is="currentTab.component"></コンポーネント>
</div>
<スクリプト>
 const {createApp} = Vue
 定数タブ = [
  {
  名前: 'ホーム'、
  成分: {
   テンプレート: `<div style="border: 1px solid;">ホーム コンポーネント</div>`
  }
  },
  {
  名前: 'My',
  成分: {
   テンプレート: `<div style="border: 1px solid;">私のコンポーネント</div>`
  }
 }]
 const app = createApp({
  データ() {
  戻る {
   タブ、
   現在のタブ: タブ[0]
  }
  },
 });
 app.mount('#app')
</スクリプト>

上記の例では、コンポーネント組み込みコンポーネントの is プロパティは、値がオブジェクトである currentTab オブジェクトのコンポーネント プロパティにバインドされています。ユーザーが Tab ボタンをクリックすると、currentTab の値が動的に更新され、currentTab.component の値も変更され、コンポーネントを動的に切り替える機能が実現されます。動的コンポーネントは切り替えられるたびに再作成されることに注意してください。ただし、シナリオによっては、繰り返しの再レンダリングによって発生するパフォーマンスの問題を回避するために、これらのコンポーネントの状態を保持する必要がある場合があります。

この問題に対しては、Vue 3 の別の組み込みコンポーネントである keep-alive を使用して、動的コンポーネントをラップすることができます。例えば:

<キープアライブ>
 <コンポーネント :is="現在のタブ"></コンポーネント>
</キープアライブ> 

キープアライブ組み込みコンポーネントの主な目的は、コンポーネントの状態を保持したり、再レンダリングを回避したりすることです。動的コンポーネントをラップするために使用される場合、非アクティブなコンポーネント インスタンスは破棄されるのではなく、キャッシュされます。キープアライブコンポーネントの内部動作原理については、Abaoge が後ほど特別な記事を書いて分析する予定です。興味のある方は、Vue 3.0 アドバンストシリーズに注目してください。

3. アバオ兄弟は何か言いたいことがある

3.1 コンポーネント組み込みコンポーネントの他に、どのような組み込みコンポーネントがありますか?

この記事で紹介したコンポーネントとキープアライブの組み込みコンポーネントに加えて、Vue 3 では、トランジション、トランジショングループ、スロット、テレポートの組み込みコンポーネントも提供されます。

3.2 グローバル コンポーネントとローカル コンポーネントの登録の違いは何ですか?

グローバルコンポーネントの登録

const { createApp, h } = Vue
定数アプリ = createApp({});
app.component('コンポーネント-a', {
 テンプレート: "<p>私はコンポーネント A です</p>"
});

app.component メソッドを使用して登録されたグローバル コンポーネントは、アプリ アプリケーション オブジェクトのコンテキスト オブジェクトに保存されます。コンポーネント オブジェクトの components プロパティを通じて登録されたローカル コンポーネントは、コンポーネント インスタンスに保存されます。

ローカルコンポーネントの登録

const { createApp, h } = Vue
定数アプリ = createApp({});
const componentA = () => h('div', '私はコンポーネントAです');
app.component('コンポーネントb', {
 コンポーネント:
 'コンポーネント-a': コンポーネントA
 },
 テンプレート: `<div>
 私はコンポーネントBであり、内部的にコンポーネントAを使用しています
 <コンポーネント-a></コンポーネント-a> 
 </div>`
})

グローバルおよびローカルに登録されたコンポーネントの解決

// パッケージ/runtime-core/src/helpers/resolveAssets.ts
関数resolveAsset(
 タイプ: typeof COMPONENTS | typeof DIRECTIVES、
 名前: 文字列、
 警告がない = true
){
 const インスタンス = 現在のレンダリングインスタンス || 現在のインスタンス
 if (インスタンス) {
 const コンポーネント = インスタンス.type
 // 処理ロジックのほとんどを省略 const res =
  // ローカル登録 // まず、mixin または extends を持つコンポーネントの instance[type] をチェックします。
  解決(instance[type] || (Component as ComponentOptions)[type], name) ||
  // グローバル登録resolve(instance.appContext[type], name)
 戻り値
 }
}

3.3 動的コンポーネントは他のプロパティをバインドできますか?

バインディングのサポートに加えて、コンポーネント組み込みコンポーネントは他の属性バインディングとイベント バインディングもサポートします。

<component :is="currentTab.component" :name="name" @click="sayHi"></component>

ここで、Abao はオンライン ツール Vue 3 Template Explorer を使用して上記のテンプレートをコンパイルします。

定数_Vue = Vue
関数 render(_ctx, _cache, $props, $setup, $data, $options) を返します {
 (_ctx) で{
 const { 解決動的コンポーネント: _resolveDynamicComponent, 
  ブロックを開く: _openBlock、ブロックを作成する: _createBlock } = _Vue

 戻り値 (_openBlock()、_createBlock(_resolveDynamicComponent(currentTab.component)、{
  名前: 名前、
  onClick: こんにちはと言う
 }, null, 8 /* PROPS */, ["name", "onClick"]))
 }
}

上記のレンダリング関数から、_resolveDynamicComponent 関数呼び出しに変換される is バインディングを除いて、他の属性バインディングは通常どおり props オブジェクトに解決されることがわかります。

上記は、vue3 の動的コンポーネントがどのように動作するかの詳細な内容です。vue3 の動的コンポーネントの詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • Vue 動的コンポーネントと v-once ディレクティブの実装
  • Vue コンポーネントの切り替え、動的コ​​ンポーネント、コンポーネントのキャッシュについて
  • vue.js 動的コンポーネントの詳細な説明
  • Vueは動的コンポーネントを使用してTAB切り替え効果を実現します
  • Vue の動的コンポーネントと非同期コンポーネントの詳細な理解
  • Vueコンポーネントの動的コンポーネントの詳細な説明

<<:  MySQL のデバッグと最適化に関する 101 のヒントを共有する

>>:  Docker Compose で環境変数を参照する方法の例

推薦する

ベンダー プレフィックス: ブラウザ エンジン プレフィックスが必要なのはなぜですか?

ベンダープレフィックスとは何ですか?ベンダー プレフィックス - ブラウザー エンジン プレフィック...

フロア効果を実現するためのJavaScript

この記事では、フロア効果を実現するためのJavaScriptの具体的なコードを参考までに紹介します。...

Tomcat の静的ページ (html) で中国語の文字化けが発生する問題の究極の解決策

tomcatでは、jspは文字化けしませんが、htmlの中国語は文字化けします理由はいくつかあります...

使用場所によって混乱しやすいXHTMLタグ

<br />jb51.net では、常に記事のセマンティクスを重視してきましたが、HTM...

最適なウェブページ幅とその互換性のある実装方法

1. Web ページをデザインするときに、幅を決定するのは非常に面倒な作業です。 jb51.net ...

MySQL 5.7 JSON 型の使用の詳細

JSON は、言語に依存しないテキスト形式を使用する軽量のデータ交換形式で、XML に似ていますが、...

Dockeにredisをインストールする方法

1. redisイメージを検索する docker 検索 redis 2. Redisイメージをダウン...

CentOS7.8 に mysql 8.0.20 をインストールするための詳細なチュートリアル

1. MySQLソフトウェアをインストールするMySQL 公式 Yum リポジトリ、MySQL バー...

Nginx サービス クイック スタート チュートリアル

目次1. Nginx の紹介1. Nginx とは何ですか? 2. Nginx を使用する理由3. ...

ドロップダウンメニュー効果を実現するJavaScript

参考までに、JavaScriptを使用してドロップダウンメニューを実装します。具体的な内容は次のとお...

MySQL における主キーが 0 であることと主キーの自己選択制約の関係についての詳しい説明 (詳細)

序文この記事は主にMySQLの主キー0と主キー自己排除制約の関係を紹介し、皆さんの参考と学習のために...

Vue で 3D タグ クラウドを実装するための詳細なコード

プレビュー: コード:ページセクション: <テンプレート> <div class=...

あなたをエキスパートに見せるための 13 個の JavaScript ワンライナー

目次1. ランダムなブール値( true / false )を取得する2. 指定された日付が営業日で...

MySQL コマンドラインでよく使われる 18 個のコマンド

日常的なウェブサイトの保守と管理では、多くの SQL ステートメントが使用されます。熟練して使用する...

2015-2016年に主流となるインタラクティブ体験のトレンド

5月の最も重要なインタラクティブデザイン記事!今年、Baiduのデザイナーは体験の観点から出発し、大...