1. カスタム指示1. グローバルカスタム指示を登録するconst アプリ = Vue.createApp({}) // グローバルカスタムディレクティブ v-focus を登録する app.directive('focus', { // バインドされた要素がDOMにマウントされたときに呼び出されます。mounted(el) { // 要素にフォーカス el.focus() } }) 2. グローバルカスタム指示を使用する<div id="アプリ"> <入力vフォーカス /> </div> 3. 完全な使用例<div id="アプリ"> <入力vフォーカス /> </div> <スクリプト> const {createApp} = Vue const app = Vue.createApp({}) // ① app.directive('focus', { // ② // バインドされた要素がDOMにマウントされたときに呼び出されます。mounted(el) { el.focus() // 要素にフォーカス } }) app.mount('#app') // ③ </スクリプト> ページが読み込まれると、ページ内の入力ボックス要素が自動的にフォーカスを取得します。この例のコードは比較的単純で、主に App オブジェクトの作成、グローバル カスタム命令の登録、アプリケーションのマウントという 3 つのステップで構成されています。 App オブジェクトの作成の詳細については、後続の記事で別途紹介します。以下では、残りの 2 つのステップの分析に焦点を当てます。まず、グローバルカスタム命令を登録するプロセスを分析しましょう。 2. グローバルカスタム指示の登録プロセス上記の例では、アプリ オブジェクトの directive メソッドを使用して、グローバル カスタム ディレクティブを登録します。 app.directive('focus', { // バインドされた要素がDOMにマウントされたときに呼び出されます。mounted(el) { el.focus() // 要素にフォーカス } }) もちろん、グローバル カスタム ディレクティブを登録するだけでなく、コンポーネントはディレクティブ オプションも受け入れるため、ローカル ディレクティブを登録することもできます。 ディレクティブ: { 集中: マウント(el) { el.フォーカス() } } } 上記の例では、runtime-core/src/apiCreateApp.ts ファイルで定義されている app.directive メソッドを使用します。 // パッケージ/ランタイムコア/src/apiCreateApp.ts エクスポート関数createAppAPI<HostElement>( レンダリング: RootRenderFunction、 水和物?: RootHydrateFunction ): CreateAppFunction<ホスト要素> { 関数createApp(rootComponent, rootProps = null)を返す{ 定数コンテキスト = createAppContext() isMounted = false とします const app: App = (context.app = { // 一部のコードを省略 _context: context, // グローバルディレクティブを登録または取得するために使用されます。 ディレクティブ(名前: 文字列、ディレクティブ?: ディレクティブ) { __DEV__ の場合 { 検証ディレクティブ名(名前) } if (!ディレクティブ) { context.directives[name]をanyとして返す } if (__DEV__ && context.directives[name]) { 警告(`ディレクティブ "${name}" は既にターゲット アプリに登録されています。`) } context.directives[name] = ディレクティブ 返品アプリ }, 返品アプリ } } 上記のコードを観察すると、ディレクティブ メソッドが次の 2 つのパラメーターをサポートしていることがわかります。
名前パラメータは比較的単純なので、Directive タイプの directive パラメータの分析に重点を置きます。 // パッケージ/ランタイムコア/src/directives.ts エクスポート型ディレクティブ<T = any, V = any> = | オブジェクトディレクティブ<T, V> | 関数ディレクティブ<T, V> 上記から、Directive 型はユニオン型に属していることがわかります。そのため、ObjectDirective 型と FunctionDirective 型の分析を続ける必要があります。ここではまず ObjectDirective 型の定義を見てみましょう。 // パッケージ/ランタイムコア/src/directives.ts エクスポートインターフェースObjectDirective<T = any, V = any> { 作成されましたか?: DirectiveHook<T, null, V> beforeMount?: DirectiveHook<T, null, V> マウントされているか: DirectiveHook<T, null, V> beforeUpdate?: DirectiveHook<T, VNode<any, T>, V> 更新されましたか?: DirectiveHook<T, VNode<any, T>, V> beforeUnmount?: DirectiveHook<T, null, V> マウント解除されましたか?: DirectiveHook<T, null, V> getSSRProps?: SSRDirectiveHook } このタイプはオブジェクトタイプのディレクティブを定義します。オブジェクトの各プロパティはディレクティブのライフサイクルのフックを表します。 FunctionDirective 型は関数型ディレクティブを表します。 // パッケージ/ランタイムコア/src/directives.ts エクスポート型 FunctionDirective<T = any, V = any> = DirectiveHook<T, any, V> エクスポート型 DirectiveHook<T = any, Prev = VNode<any, T> | null, V = any> = ( エル: T, バインディング: DirectiveBinding<V>、 vnode: VNode<任意, T>, 前へVNode: 前へ ) => 無効 ディレクティブ型を紹介した後、前の例を見直してみましょう。より明確になると思います。 app.directive('focus', { // バインドされた要素がDOMにマウントされたときにトリガーされるmounted(el) { el.focus() // 要素にフォーカス } }) 上記の例では、app.directive メソッドを呼び出してカスタム フォーカス ディレクティブを登録すると、次のロジックが実行されます。 ディレクティブ(名前: 文字列、ディレクティブ?: ディレクティブ) { if (__DEV__) { // カスタムディレクティブ名と既存の組み込みディレクティブ名の競合を回避するvalidateDirectiveName(name) } if (!directive) { // 名前に対応するディレクティブオブジェクトを取得する return context.directives[name] as any } if (__DEV__ && context.directives[name]) { 警告(`ディレクティブ "${name}" は既にターゲット アプリに登録されています。`) } context.directives[name] = directive // グローバルディレクティブを登録する return app } focus ディレクティブが正常に登録されると、次の図に示すように、ディレクティブはコンテキスト オブジェクトの directives プロパティに保存されます。 名前が示すように、コンテキストはアプリケーションを表すコンテキスト オブジェクトですが、このオブジェクトはどのように作成されるのでしょうか。実際、このオブジェクトは createAppContext 関数によって作成されます。 定数コンテキスト = createAppContext() createAppContext 関数は、runtime-core/src/apiCreateApp.ts ファイルで定義されています。 // パッケージ/ランタイムコア/src/apiCreateApp.ts エクスポート関数createAppContext(): AppContext { 戻る { app: null は任意、 設定: { isNativeTag: いいえ、 パフォーマンス: false、 グローバルプロパティ: {}, オプションマージ戦略: {}, isCustomElement: いいえ、 errorHandler: 未定義、 warnHandler: 未定義 }, ミックスイン: [], コンポーネント: {}, ディレクティブ: {}, 提供: Object.create(null) } } これを見ると、グローバルカスタム命令を登録する内部処理ロジックは実は非常に単純だと思いませんか?では、登録されたフォーカス コマンドはいつ呼び出されるのでしょうか?この質問に答えるには、別のステップであるアプリケーションのマウントを分析する必要があります。 3. アプリケーションのマウントプロセスアプリケーションのマウント プロセスをより直感的に理解するために、Abaoge は Chrome 開発者ツールを使用して、アプリケーションのマウントの主なプロセスを記録しました。 上の図から、アプリケーションのマウント時の主なプロセスがわかります。さらに、図から命令に関連する関数resolveDirectiveも見つかりました。明らかに、この関数は命令を解析するために使用され、この関数はレンダリング メソッドで呼び出されます。ソース コードには、関数の定義が見つかりました。 // パッケージ/runtime-core/src/helpers/resolveAssets.ts エクスポート関数resolveDirective(name: string): ディレクティブ | undefined { 戻り値:resolveAsset(DIRECTIVES, name) } resolveDirective 関数内では、特定の解決操作を実行するために、resolveAsset 関数が引き続き呼び出されます。 resolveAsset 関数の特定の実装を分析する前に、resolveDirective 関数内にブレークポイントを追加して、render メソッドの「美しさ」を確認しましょう。 上の画像では、 focus ディレクティブに関連付けられた _resolveDirective("focus") 関数呼び出しが表示されています。すでにわかっているように、resolveAsset 関数は、resolveDirective 関数内で引き続き呼び出されます。この関数の具体的な実装は次のとおりです。 // パッケージ/runtime-core/src/helpers/resolveAssets.ts 関数resolveAsset( タイプ: typeof COMPONENTS | typeof DIRECTIVES、 名前: 文字列、 警告がない = true ){ const インスタンス = 現在のレンダリングインスタンス || 現在のインスタンス if (インスタンス) { const コンポーネント = インスタンス.type // 解析コンポーネントの処理ロジックを省略 const res = // ローカル登録resolve(instance[type] || (Component as ComponentOptions)[type], name) || // グローバル登録resolve(instance.appContext[type], name) 戻り値 } そうでない場合 (__DEV__) { 警告( `${capitalize(type.slice(0, -1))}を解決します` + `render() または setup() でのみ使用できます。` ) } } focus ディレクティブを登録するときにグローバル登録メソッドが使用されるため、解析プロセスは、resolve(instance.appContext[type], name) ステートメントを実行します。ここで、resolve メソッドは次のように定義されます。 関数resolve(レジストリ: Record<文字列、任意> | 未定義、名前: 文字列) { 戻る ( レジストリ && (レジストリ[名前] || レジストリ[camelize(名前)] || レジストリ[大文字にする(キャメル文字にする(名前))]) ) } 上記の処理フローを分析すると、グローバルに登録された命令を解析するときに、登録された命令オブジェクトが、resolve 関数を通じてアプリケーション コンテキスト オブジェクトから取得されることがわかります。 _directive_focus ディレクティブ オブジェクトを取得した後、render メソッドは _withDirectives 関数を呼び出し続け、ディレクティブを VNode オブジェクトに追加します。この関数は、runtime-core/src/directives.ts ファイルで定義されています。 // パッケージ/ランタイムコア/src/directives.ts 関数withDirectives<T extends VNode>(をエクスポートする vノード: T、 ディレクティブ: DirectiveArguments ): T { const internalInstance = currentRenderingInstance // 現在レンダリングされているインスタンスを取得します const instance = internalInstance.proxy 定数バインディング: DirectiveBinding[] = vnode.dirs || (vnode.dirs = []) (i = 0 とします; i < directives.length; i++) { let [dir, value, arg, modifiers = EMPTY_OBJ] = ディレクティブ[i] // 他のフック関数に関係なく、マウントおよび更新時に同じ動作をトリガーします。if (isFunction(dir)) { // 関数型命令の処理 dir = { マウント: dir、 更新:dir } を ObjectDirective として } バインディング.push({ ディレクター、 実例、 価値、 古い値: void 0, 引数、 修飾語 }) } vnodeを返す } ノードには複数のディレクティブが適用される可能性があるため、withDirectives 関数は VNode オブジェクトに dirs プロパティを定義し、このプロパティの値は配列になります。前の例では、withDirectives 関数を呼び出した後、次の図に示すように、 dirs プロパティが VNode オブジェクトに追加されます。 上記の分析により、コンポーネントの render メソッドで、withDirectives 関数を通じて対応する VNode オブジェクトにディレクティブを登録することがすでにわかっています。では、 focus ディレクティブで定義されたフックはいつ呼び出されるのでしょうか?分析を続ける前に、まず命令オブジェクトでサポートされているフック関数を紹介しましょう。 ディレクティブ定義オブジェクトは、次のフック関数を提供できます (すべてオプション)。
これらのフック関数を紹介した後、先ほど紹介した ObjectDirective 型を確認しましょう。 // パッケージ/ランタイムコア/src/directives.ts エクスポートインターフェースObjectDirective<T = any, V = any> { 作成されましたか?: DirectiveHook<T, null, V> beforeMount?: DirectiveHook<T, null, V> マウントされているか?: DirectiveHook<T, null, V> beforeUpdate?: DirectiveHook<T, VNode<any, T>, V> 更新されましたか?: DirectiveHook<T, VNode<any, T>, V> beforeUnmount?: DirectiveHook<T, null, V> マウント解除されましたか?: DirectiveHook<T, null, V> getSSRProps?: SSRDirectiveHook さて、 focus ディレクティブで定義されたフックがいつ呼び出されるか分析してみましょう。同様に、Abaoge は focus コマンドのマウントされたメソッドにブレークポイントを追加します。 図の右側のコール スタックには、invokeDirectiveHook 関数があります。この関数は、命令に登録されたフックを呼び出すものであることは明らかです。スペースの都合上、具体的な詳細には触れません。興味のある方はご自身でデバッグしてください。 4. アバオ兄弟は何か言いたいことがある4.1 Vue 3 の組み込みディレクティブとは何ですか?グローバル カスタム命令の登録を導入するプロセスで、カスタム命令名と既存の組み込み命令名との競合を避けるためにカスタム命令名を検証するために使用される、validateDirectiveName 関数を確認しました。 // パッケージ/ランタイムコア/src/directives.ts エクスポート関数validateDirectiveName(name: string) { if (isBuiltInDirective(名前)) { warn('組み込みディレクティブ ID をカスタム ディレクティブ ID として使用しないでください: ' + 名前) } } validateDirectiveName 関数内では、isBuiltInDirective(name) ステートメントを使用して、組み込みディレクティブであるかどうかを判断します。 const isBuiltInDirective = /*#__PURE__*/ makeMap( 'bind、cloak、else-if、else、for、html、if、model、on、once、pre、show、slot、text' ) 上記のコードの makeMap 関数は、マップ オブジェクト (Object.create(null)) を生成し、マップ オブジェクトにキーが存在するかどうかを検出する関数を返すために使用されます。さらに、上記のコードを通じて、Vue 3 が提供する組み込み命令が何であるかを明確に理解できます。 4.2 命令の種類は何種類ありますか?Vue 3 では、ディレクティブは ObjectDirective と FunctionDirective の 2 つのタイプに分かれています。 // パッケージ/ランタイムコア/src/directives.ts エクスポート型ディレクティブ<T = any, V = any> = | オブジェクトディレクティブ<T, V> | 関数ディレクティブ<T, V> オブジェクトディレクティブ エクスポートインターフェースObjectDirective<T = any, V = any> { 作成されましたか?: DirectiveHook<T, null, V> beforeMount?: DirectiveHook<T, null, V> マウントされているか?: DirectiveHook<T, null, V> beforeUpdate?: DirectiveHook<T, VNode<any, T>, V> 更新されましたか?: DirectiveHook<T, VNode<any, T>, V> beforeUnmount?: DirectiveHook<T, null, V> マウント解除されましたか?: DirectiveHook<T, null, V> getSSRProps?: SSRDirectiveHook } 関数ディレクティブ エクスポート型 FunctionDirective<T = any, V = any> = DirectiveHook<T, any, V> エクスポート型 DirectiveHook<T = any, Prev = VNode<any, T> | null, V = any> = ( エル: T, バインディング: DirectiveBinding<V>、 vnode: VNode<任意, T>, 前へVNode: 前へ ) => 無効 マウントおよび更新時に同じ動作をトリガーし、他のフック関数を気にしない場合。次に、コールバック関数をディレクティブに渡すことで実行できます。 app.directive('pin', (el, binding) => { el.style.position = '固定' const s = binding.arg || 'top' el.style[s] = バインディング値 + 'px' }) 4.3 グローバル命令の登録とローカル命令の登録の違いは何ですか?グローバルディレクティブの登録 app.directive('focus', { // バインドされた要素がDOMにマウントされたときに呼び出されます。mounted(el) { el.focus() // 要素にフォーカス } }); ローカル指令の登録 const コンポーネント = defineComponent({ ディレクティブ: { 集中: マウント(el) { el.フォーカス() } } }, 与える() { const { ディレクティブ } = this.$options; [withDirectives(h('input'), [[directives.focus, ]])] を返します } }); グローバルおよびローカル登録命令の解析 // パッケージ/runtime-core/src/helpers/resolveAssets.ts 関数resolveAsset( タイプ: typeof COMPONENTS | typeof DIRECTIVES、 名前: 文字列、 警告がない = true ){ const インスタンス = 現在のレンダリングインスタンス || 現在のインスタンス if (インスタンス) { const コンポーネント = インスタンス.type // 解析コンポーネントの処理ロジックを省略 const res = // ローカル登録resolve(instance[type] || (Component as ComponentOptions)[type], name) || // グローバル登録resolve(instance.appContext[type], name) 戻り値 } } 4.4 組み込み命令とカスタム命令によって生成されるレンダリング関数の違いは何ですか?組み込み命令とカスタム命令によって生成されるレンダリング関数の違いを理解するために、Abaoge は組み込み命令 v-if、v-show、カスタム命令 v-focus を例にとり、Vue 3 Template Explorer オンライン ツールを使用してレンダリング関数をコンパイルおよび生成します。 v-if 組み込みディレクティブ <input v-if="isShow" /> 定数_Vue = Vue 関数 render(_ctx, _cache, $props, $setup, $data, $options) を返します { (_ctx) で{ const { createVNode: _createVNode, openBlock: _openBlock, ブロックを作成: _createBlock、コメントVノードを作成: _createCommentVNode } = _Vue 戻り値 isShow ? (_openBlock()、_createBlock("input", { キー: 0 })) : _createCommentVNode("v-if", true) } } v-if 命令では、コンパイル後に ?: 三項演算子を使用して、ノードを動的に作成する機能を実装します。 v-show 組み込みディレクティブ <input v-show="isShow" /> 定数_Vue = Vue 関数 render(_ctx, _cache, $props, $setup, $data, $options) を返します { (_ctx) で{ const { vShow: _vShow、 createVNode: _createVNode、 withDirectives: _withDirectives、 ブロックを開く: _openBlock、ブロックを作成する: _createBlock } = _Vue _withDirectives((_openBlock(), _createBlock("input", null, null, 512 /* NEED_PATCH */)), [ を返します [_v表示、is表示] ]) } } 上記の例の vShow ディレクティブは、packages/runtime-dom/src/directives/vShow.ts ファイルで定義されています。このディレクティブは ObjectDirective タイプであり、beforeMount、mounted、updated、beforeUnmount の 4 つのフックを定義します。 v-focus カスタムディレクティブ <入力vフォーカス /> 定数_Vue = Vue 関数 render(_ctx, _cache, $props, $setup, $data, $options) を返します { (_ctx) で{ const { 解決ディレクティブ: _resolveDirective、 作成VNode: _createVNode、 withDirectives: _withDirectives、openBlock: _openBlock、createBlock: _createBlock } = _Vue 定数 _directive_focus = _resolveDirective("focus") _withDirectives((_openBlock(), _createBlock("input", null, null, 512 /* NEED_PATCH */)), [ を返します [_directive_focus] ]) } } v-focus 命令と v-show 命令によって生成されたレンダリング関数を比較すると、v-focus カスタム命令と v-show 組み込み命令の両方が withDirectives 関数を通じて VNode オブジェクトに命令を登録することがわかります。組み込み命令と比較して、カスタム命令には追加の命令解析プロセスがあります。 さらに、入力要素に v-show ディレクティブと v-focus ディレクティブの両方が適用されている場合は、_withDirectives 関数を呼び出すときに 2 次元配列が使用されます。 <input v-show="isShow" v-focus /> 定数_Vue = Vue 関数 render(_ctx, _cache, $props, $setup, $data, $options) を返します { (_ctx) で{ const { vShow: _vShow、resolveDirective: _resolveDirective、createVNode: _createVNode、 withDirectives: _withDirectives、openBlock: _openBlock、createBlock: _createBlock } = _Vue 定数 _directive_focus = _resolveDirective("focus") _withDirectives((_openBlock(), _createBlock("input", null, null, 512 /* NEED_PATCH */)), [ を返します [_v表示、is表示]、 [_directive_focus] ]) } } 4.5 レンダリング関数にディレクティブを適用するにはどうすればよいですか?テンプレートにディレクティブを適用するだけでなく、前に紹介した withDirectives 関数を使用して、レンダリング関数に指定したディレクティブを簡単に適用できます。 <div id="アプリ"></div> <スクリプト> const { createApp, h, vShow, defineComponent, withDirectives } = Vue const コンポーネント = defineComponent({ データ() { 戻り値: true } }, 与える() { [withDirectives(h('div', '私はアバオ兄弟です'), [[vShow, this.value]])] を返します } }); const app = Vue.createApp(コンポーネント) app.mount('#app') </スクリプト> この記事では、Abao 兄弟が主に、Vue 3 で命令をカスタマイズする方法と、グローバル命令とローカル命令を登録する方法を紹介します。誰もがカスタム指示書の関連知識をより深く理解できるように、アバオ兄弟はソースコードの観点から指示書の登録および適用プロセスを分析しました。 今後の記事では、アバオ兄弟が特別な指示をいくつか紹介し、もちろん双方向バインディングの原理の分析に焦点を当てます。ご興味があれば、お見逃しなく。 以上がVue 3.0カスタム命令の使い方の詳しい紹介です。Vue 3.0カスタム命令の使い方の詳細については、123WORDPRESS.COMの他の関連記事もご覧ください。 以下もご興味があるかもしれません:
|
<<: .NETCore Dockerはコンテナ化とプライベートイメージリポジトリ管理を実装します
>>: MySQL が InnoDB テーブルが独立したテーブルスペースか共有テーブルスペースかを判断する方法の詳細な説明
序文スクロールやサイズ変更などのスクロール イベントがトリガーされると、トリガーの頻度が非常に高くな...
折りたたまれたヘッダーは、特別オファーや重要なお知らせなど、ユーザーにとって重要な情報を表示するのに...
{ {}} 値を取得すると、タグの元のコンテンツはクリアされませんv-textは値を取得し、タグの元...
この記事では、Nodejs 開発プロセスで遭遇する配列の特性によって発生する問題と解決策、および配列...
問題を見つける最近 Django を学習しているのですが、MySQL データと組み合わせてデータを挿...
目次1. Dockerの設定2. レジストリとネットワークを作成する3. コンテナを起動する環境説明...
forループ基本的な構文形式: for(変数の初期化; 条件式; 演算式){ループ本体ステートメント...
Canvas は HTML5 の新しいタグです。js を使用して Canvas 描画 API を操作...
目次概要パフォーマンス.nowコンソール.time時間精度を短縮注意事項分割して征服する入力値に注意...
仕事の都合上、最近 HTML を PDF に変換する機能について調べることに時間を費やしました。 H...
この記事では、パズル効果を実現するためのネイティブjsの具体的なコードを参考までに共有します。具体的...
予備的注釈1.Vue2.xとVue3.xの違い: Vue 3.x にはヘルパー関数はありません。 V...
この記事では、参考までにMySQL 8.0.15圧縮版のインストール方法を紹介します。具体的な内容は...
js 配列はどこでも使用されているため、おそらく誰もがよく知っているでしょうが、配列クラス (疑似配...
CSS の 2D 変換を使用すると、移動、回転、拡大縮小、変形などの基本的な変換操作を 2 次元空間...