<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      [vue3] Vue3 自定義指令及原理探索

      Vue3除了內置的v-onv-bind等指令,還可以自定義指令。

      注冊自定義指令

      全局注冊

      const app = createApp({})
      
      // 使 v-focus 在所有組件中都可用
      app.directive('focus', {
        /* ... */
      })
      

      局部選項式注冊

      在沒有使用<script setup>的情況下,使用選項式語法,在direactives中注冊事件。

      export default {
        setup() {
          /*...*/
        },
        directives: {
          // 在模板中啟用 v-focus
          focus: {
            /* ... */
          }
        }
      }
      

      隱式注冊

      <script setup>內,任何v開頭并遵循駝峰式命名的變量都可以用作一個自定義指令。

      <script setup>
      // 在模板中啟用 v-focus
      const vFocus = {
        mounted: (el) => el.focus()
      }
      </script>
      
      <template>
        <input v-focus />
      </template>
      

      實現自定義指令

      指令的工作原理在于:在特定的時期為綁定的節點做特定的操作。

      通過生命周期hooks實現自定義指令的邏輯。

      const myDirective = {
        // 在綁定元素的 attribute 前
        // 或事件監聽器應用前調用
        created(el, binding, vnode) {
          // 下面會介紹各個參數的細節
        },
        // 在元素被插入到 DOM 前調用
        beforeMount(el, binding, vnode) {},
        // 在綁定元素的父組件
        // 及他自己的所有子節點都掛載完成后調用
        mounted(el, binding, vnode) {},
        // 綁定元素的父組件更新前調用
        beforeUpdate(el, binding, vnode, prevVnode) {},
        // 在綁定元素的父組件
        // 及他自己的所有子節點都更新后調用
        updated(el, binding, vnode, prevVnode) {},
        // 綁定元素的父組件卸載前調用
        beforeUnmount(el, binding, vnode) {},
        // 綁定元素的父組件卸載后調用
        unmounted(el, binding, vnode) {}
      }
      

      其中最常用的是mountedupdated

      簡化形式

      app.directive('color', (el, binding) => {
        // 這會在 `mounted` 和 `updated` 時都調用
        el.style.color = binding.value
      })
      

      參數

      • el:指令綁定到的元素。這可以用于直接操作 DOM。
      • binding:一個對象,包含以下屬性。
        • value:傳遞給指令的值。例如在 v-my-directive="1 + 1" 中,值是 2
        • oldValue:之前的值,僅在 beforeUpdateupdated 中可用。無論值是否更改,它都可用。
        • arg:傳遞給指令的參數 (如果有的話)。例如在 v-my-directive:foo 中,參數是 "foo"
        • modifiers:一個包含修飾符的對象 (如果有的話)。例如在 v-my-directive.foo.bar 中,修飾符對象是 { foo: true, bar: true }
        • instance:使用該指令的組件實例。
        • dir:指令的定義對象。
      • vnode:代表綁定元素的底層 VNode。
      • prevVnode:代表之前的渲染中指令所綁定元素的 VNode。僅在 beforeUpdateupdated 鉤子中可用。

      除了 el 外,其他參數都是只讀的。

      指令的工作原理

      全局注冊的指令

      先看一下全局注冊的指令。

      全局注冊是通過appdirective方法注冊的,而app是通過createApp函數創建的。

      源碼位置core/packages/runtime-core/src/apiCreateApp.ts at main · vuejs/core (github.com)

      createApp的實現中,可以看到創建了一個app對象,帶有一個directive方法的實現,就是全局注冊指令的API。

      const app: App = (context.app = {
          ...
          directive(name: string, directive?: Directive) {
              if (__DEV__) {
                  validateDirectiveName(name)
              }
      
              if (!directive) {
                  return context.directives[name] as any
              }
              if (__DEV__ && context.directives[name]) {
                  warn(`Directive "${name}" has already been registered in target app.`)
              }
              context.directives[name] = directive
              return app
          },
          ...
      })
      

      如代碼中所示:

      • 如果調用app.directive(name),那么就會返回指定的指令對象;
      • 如果調用app.directive(name, directive),那么就會注冊指定的指令對象,記錄在context.directives對象上。

      局部注冊的指令

      局部注冊的指令會被記錄在組件實例上。

      源碼位置core/packages/runtime-core/src/component.ts at main · vuejs/core (github.com)

      這里省略了大部分代碼,只是想展示組件的instance上是有directives屬性的,就是它記錄著局部注冊的指令。

      export function createComponentInstance(
        vnode: VNode,
        parent: ComponentInternalInstance | null,
        suspense: SuspenseBoundary | null,
      ) {
        ...
        const instance: ComponentInternalInstance = {
          ...
      
          // local resolved assets
          components: null,
          directives: null,
        }
        ...
      }
      

      instance.directives被初始化為null,接下來我們看一下開發時注冊的局部指令是如何被記錄到這里的。

      編譯階段

      這一部分我還不太理解,但是大致找到了源碼的位置:

      core/packages/compiler-core/src/transforms/transformElement.ts at main · vuejs/core (github.com)

      // generate a JavaScript AST for this element's codegen
      export const transformElement: NodeTransform = (node, context) => {
        // perform the work on exit, after all child expressions have been
        // processed and merged.
        return function postTransformElement() {
          node = context.currentNode!
      	......
          // props
          if (props.length > 0) {
            const propsBuildResult = buildProps(
              node,
              context,
              undefined,
              isComponent,
              isDynamicComponent,
            )
            ......
            const directives = propsBuildResult.directives
            vnodeDirectives =
              directives && directives.length
                ? (createArrayExpression(
                    directives.map(dir => buildDirectiveArgs(dir, context)),
                  ) as DirectiveArguments)
                : undefined
      	  ......
          }
          ......
        }
      }
      

      大致就是通過buildProps獲得了directives數組,然后記錄到了vnodeDirectives

      buildProps中關于directives的源碼大概在:core/packages/compiler-core/src/transforms/transformElement.ts at main · vuejs/core (github.com)

      代碼比較長,主要是先嘗試匹配v-onv-bind等內置指令并做相關處理,最后使用directiveTransform做轉換:

      // buildProps函數的一部分代碼
      //=====================================================================
      const directiveTransform = context.directiveTransforms[name]
      if (directiveTransform) {
          // has built-in directive transform.
          const { props, needRuntime } = directiveTransform(prop, node, context)
          !ssr && props.forEach(analyzePatchFlag)
          if (isVOn && arg && !isStaticExp(arg)) {
              pushMergeArg(createObjectExpression(props, elementLoc))
          } else {
              properties.push(...props)
          }
          if (needRuntime) {
              runtimeDirectives.push(prop)
              if (isSymbol(needRuntime)) {
                  directiveImportMap.set(prop, needRuntime)
              }
          }
      } else if (!isBuiltInDirective(name)) {
          // no built-in transform, this is a user custom directive.
          runtimeDirectives.push(prop)
          // custom dirs may use beforeUpdate so they need to force blocks
          // to ensure before-update gets called before children update
          if (hasChildren) {
              shouldUseBlock = true
          }
      }
      

      將自定義指令添加到runtimeDirectives里,最后作為buildProps的返回值之一。

      // buildProps函數的返回值
      //=====================================
      return {
          props: propsExpression,
          directives: runtimeDirectives,
          patchFlag,
          dynamicPropNames,
          shouldUseBlock,
      }
      

      運行時階段

      這里介紹一下Vue3提供的一個關于template與渲染函數的網站:https://template-explorer.vuejs.org/

      這里我寫了一些簡單的指令(事實上很不合理...就是隨便寫寫):

      template

      <div v-loading="!ready">
        <p 
          v-color="red" 
          v-capacity="0.8"
          v-obj="{a:1, b:2}"
          >
            red font
          </p>
      </div>
      

      生成的渲染函數

      export function render(_ctx, _cache, $props, $setup, $data, $options) {
        const _directive_color = _resolveDirective("color")
        const _directive_capacity = _resolveDirective("capacity")
        const _directive_obj = _resolveDirective("obj")
        const _directive_loading = _resolveDirective("loading")
      
        return _withDirectives((_openBlock(), _createElementBlock("div", null, [
          _withDirectives((_openBlock(), _createElementBlock("p", null, [
            _createTextVNode(" red font ")
          ])), [
            [_directive_color, _ctx.red],
            [_directive_capacity, 0.8],
            [_directive_obj, {a:1, b:2}]
          ])
        ])), [
          [_directive_loading, !_ctx.ready]
        ])
      }
      

      這個網站還會在控制臺輸出AST,抽象語法樹展開太占空間了,這里就不展示了。

      • _resolveDirective 函數根據指令名稱在上下文中查找相應的指令定義,并返回一個指令對象。
      • _withDirectives(vnode, directives):將指令應用到虛擬節點 vnode 上。
        • directives:數組中的每個元素包含兩個部分:指令對象和指令的綁定值。
      resolveDirective

      源碼位置core/packages/runtime-core/src/helpers/resolveAssets.ts at main · vuejs/core (github.com)

      export function resolveDirective(name: string): Directive | undefined {
        return resolveAsset(DIRECTIVES, name)
      }
      

      調用了resolveAsset,在resolveAsset內部找到相關邏輯:(先找局部指令,再找全局指令)

      const res =
            // local registration
            // check instance[type] first which is resolved for options API
            resolve(instance[type] || (Component as ComponentOptions)[type], name) ||
            // global registration
            resolve(instance.appContext[type], name)
      

      resolve函數會嘗試匹配原始指令名、駝峰指令名、首字母大寫的駝峰:

      function resolve(registry: Record<string, any> | undefined, name: string) {
        return (
          registry &&
          (registry[name] ||
            registry[camelize(name)] ||
            registry[capitalize(camelize(name))])
        )
      }
      
      withDirective

      源碼位置core/packages/runtime-core/src/directives.ts at main · vuejs/core (github.com)

      export function withDirectives<T extends VNode>(
        vnode: T,
        directives: DirectiveArguments,
      ): T {
        // 如果當前沒有渲染實例,說明該函數未在渲染函數內使用,給出警告
        if (currentRenderingInstance === null) {
          __DEV__ && warn(`withDirectives can only be used inside render functions.`)
          return vnode
        }
        
        // 獲取當前渲染實例的公共實例
        const instance = getComponentPublicInstance(currentRenderingInstance)
        
        // 獲取或初始化 vnode 的指令綁定數組
        const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])
        
        // 遍歷傳入的指令數組
        for (let i = 0; i < directives.length; i++) {
          let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]
          
          // 如果指令存在
          if (dir) {
            // 如果指令是一個函數,將其轉換為對象形式的指令
            if (isFunction(dir)) {
              dir = {
                mounted: dir,
                updated: dir,
              } as ObjectDirective
            }
            
            // 如果指令具有 deep 屬性,遍歷其值
            if (dir.deep) {
              traverse(value)
            }
            
            // 將指令綁定添加到綁定數組中
            bindings.push({
              dir,           // 指令對象
              instance,      // 當前組件實例
              value,         // 指令的綁定值
              oldValue: void 0, // 舊值,初始為 undefined
              arg,           // 指令參數
              modifiers,     // 指令修飾符
            })
          }
        }
        
        // 返回帶有指令綁定的 vnode
        return vnode
      }
      
      

      注意

      // 如果指令是一個函數,將其轉換為對象形式的指令
      if (isFunction(dir)) {
          dir = {
              mounted: dir,
              updated: dir,
          } as ObjectDirective
      }
      

      這里就是上文提到的簡便寫法,傳入一個函數,默認在mountedupdated這兩個生命周期觸發。

      到這里,VNode就完成了指令的hooks的綁定。

      在不同的生命周期,VNode會檢查是否有指令回調,有的話就會調用。

      生命周期的相關代碼在renderer.ts文件里:core/packages/runtime-core/src/renderer.ts at main · vuejs/core (github.com)

      image-20240801020620698 image-20240801020647645

      invokeDirectiveHook的實現在core/packages/runtime-core/src/directives.ts at main · vuejs/core (github.com),此處省略。

      posted @ 2024-08-01 02:10  feixianxing  閱讀(344)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 亚洲一区二区av免费| 老熟妇性老熟妇性色| 好爽毛片一区二区三区四| 狠狠色噜噜狠狠狠777米奇小说| 九九热在线免费精品视频| 不卡国产一区二区三区| 高清中文字幕国产精品| 国产地址二永久伊甸园| 亚洲欧美偷国产日韩| 日本久久高清一区二区三区毛片| 亚洲综合国产伊人五月婷| 尤物国产精品福利在线网| 干老熟女干老穴干老女人| 18禁无遮挡啪啪无码网站破解版| 天堂av资源在线免费| 国产综合久久亚洲综合| 会泽县| 亚洲av精选一区二区| 麻豆国产成人AV在线播放| 成人亚欧欧美激情在线观看| 亚洲欧美综合一区二区三区| 四虎成人精品在永久免费| 97免费在线观看视频| 欧美日韩精品一区二区视频| 国产午夜精品久久精品电影| 国产又色又刺激高潮视频| 性猛交ⅹxxx富婆视频| 国产精品中文一区二区| 中文字幕成人精品久久不卡 | 久久亚洲国产品一区二区| 九九热精品在线视频观看| 欧美国产日韩在线三区| 国产91丝袜在线播放动漫| 精品一区二区成人码动漫| 久久精品国产亚洲av麻豆小说| 精品精品亚洲高清a毛片| 精品乱人码一区二区二区| 日韩一区二区三区女优丝袜| 北岛玲中文字幕人妻系列| 美日韩av一区二区三区| 偷拍精品一区二区三区 |