この記事では、Abaoge が Vue 3 の組み込みコンポーネントであるコンポーネントを紹介します。このコンポーネントは、「メタ コンポーネント」を動的コンポーネントとしてレンダリングするために使用されます。動的コンポーネントについてあまり詳しくなくても大丈夫です。この記事では、アバオ兄弟が具体的な例を通して動的コンポーネントの応用を紹介します。動的コンポーネントの内部構造とコンポーネント登録の間には一定の関連があるため、動的コンポーネントの内部原理を誰もがよりよく理解できるように、Abao はまずコンポーネント登録の関連知識を紹介します。 1. コンポーネントの登録1.1 グローバル登録Vue 3.0 では、アプリ オブジェクトのコンポーネント メソッドを使用して、グローバル コンポーネントを簡単に登録または取得できます。コンポーネント メソッドは、次の 2 つのパラメータをサポートします。
次に、簡単な例を見てみましょう。 <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 の他の関連記事に注目してください。 以下もご興味があるかもしれません:
|
<<: MySQL のデバッグと最適化に関する 101 のヒントを共有する
>>: Docker Compose で環境変数を参照する方法の例
この記事の例では、双方向データバインディングを実現するためのjsの具体的なコードを参考までに共有して...
この記事では、Linux システムを起動する方法について説明します。ご参考までに、詳細は以下の通りで...
この記事は主に、Vue のレスポンシブ ソース コードを理解していない、または触れたことがない人向け...
企業では、データベースの高可用性は常に最優先事項です。多くの中小企業は、MySQL マスター スレー...
目次1. シーンレイアウト2. ハンドルリスナーを追加する1. イベントの変更を監視する2. 座標設...
負荷リクエスト成功リクエストに失敗しました cmdをクリックし、ファイルパスでEnterキーを押しま...
序文通知バー コンポーネントは、比較的一般的なコンポーネントです。基本的に、すべてのサイトにこのよう...
MySQL を自分でインストールするのに 3 時間かかりました。チュートリアルはたくさんあるにもかか...
ファイル サーバーは、企業内で最も一般的に使用されるサーバーの一つであり、主にファイル共有を提供する...
概要通常、データベース内のデータを直接表示することは望ましくないため、最後の 2 つのセクションでは...
序文プロジェクトのニーズにより、ストレージ フィールドは JSON 形式で保存されます。プロジェクト...
目次Linux 環境変数とプロセスアドレス空間コードを通じて環境変数を取得するプロセスアドレス空間な...
ご存知のとおり、CSS の絶対位置はデフォルトでドキュメントに応じて設定されます。たとえば、posi...
今日、slave_exec_modeというパラメータを偶然見ました。マニュアルの説明から、このパラメ...
オプションに属性 selected = "selected" を追加すると、それ...