一篇文章告诉你Vue3指令是如何实现的

 更新时间:2022年01月27日 15:11:50   作者:chonglingliu  
在计算机技术中,指令是由指令集架构定义的单个的CPU操作,在更广泛的意义上,“指令”可以是任何可执行程序的元素的表述,例如字节码,下面这篇文章主要给大家介绍了关于Vue3指令是如何实现的相关资料,需要的朋友可以参考下

前言

Vue 指令 是指 对普通DOM元素进行底层操作的JS对象, 它们会被挂在Element VNode对象上,在Element VNode的一些生命周期中会被调用,从而可以操作Element VNode的底层DOM元素。

指令注册

指令注册 是指将指令对应的JS代码放置在某些地方,需要使用的时候可以在这些地方进行查找。

全局注册

  • 全局注册是调用app.directive('指令名称', { 指令代码 }) 来实现的
app.directive('pin', (el, binding) => {
  el.style.position = 'fixed'
  const s = binding.arg || 'top'
  el.style[s] = binding.value + 'px'
})
  • 全局注册的逻辑是将 指令名称 和 对应的指令代码 挂载在全局的context的directives对象上
<!-- apiCreateApp.js -->
directive(name: string, directive?: Directive) {
  
  // 挂载在全局的`context`的`directives`对象上
  context.directives[name] = directive
  
  return app
},

组件内注册

  • 组件内注册是在组件内添加 directives 的选项
directives: {
  pin: (el, binding) => {
    el.style.position = 'fixed'
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  }
}
  • 组件注册的逻辑是将 指令名称 和 对应的指令代码 挂载在组件实例对象的directives上
<!-- component.ts -->
export function applyOptions(instance: ComponentInternalInstance) {
  
  // 挂载在组件实例对象的`directives`上    
  instance.directives = directives
  
}

指令搜寻

指令搜寻的时机

开发者是在模板中使用指令,所以应该是在模板渲染的时候需要先搜寻到对应的指令。

不使用指令的模板<h4>指令演示</h4>渲染函数如下:

function render(_ctx, _cache) {
  with (_ctx) {
    const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue

    return (_openBlock(), _createElementBlock("h4", null, "指令演示"))
  }
}

使用指令的模板<h4 v-pin:[right]="20">指令演示</h4>渲染函数如下:

function render(_ctx, _cache) {
  with (_ctx) {
    const { createTextVNode: _createTextVNode, resolveDirective: _resolveDirective, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue

    const _directive_pin = _resolveDirective("pin")

    return _withDirectives((_openBlock(), _createElementBlock("h4", null, _hoisted_2, 512 /* NEED_PATCH */)), [
      [_directive_pin, pinPadding, direction]
    ])
  }
}

使用指令的模板需要先搜寻对应的指令,然后绑定指令到VNode

指令搜寻的逻辑

  • 指令搜寻的逻辑是先从组件实例instance的directives上寻找,如果没找到再在appContext的directives上寻找
export function resolveDirective(name: string): Directive | undefined {
  return resolveAsset(DIRECTIVES, name)
}

function resolveAsset(
  type: AssetTypes,
  name: string,
  warnMissing = true,
  maybeSelfReference = false
) {
    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)

    return res
}

指令绑定VNode

export function withDirectives<T extends VNode>(
  vnode: T,
  directives: DirectiveArguments
): T {
    
  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 (isFunction(dir)) {
      dir = {
        mounted: dir,
        updated: dir
      } as ObjectDirective
    }
    bindings.push({
      dir,
      instance,
      value,
      oldValue: void 0,
      arg,
      modifiers
    })
  }
  return vnode
}

将每个指令dir和其他一些参数 挂载在 VNode的dirs上。 其他参数是: instance组件实例, value指令的新值(本例为20),oldValue指令的旧值(本例为0),arg指令的参数(本例为right)

指令调用

指令调用是指 指令的代码什么时候被执行的? 我们最开始提到指令是对普通DOM元素进行底层操作的JS对象,所以指令的逻辑应该是在 Element VNode中进行处理的。

const mountElement = (
  vnode: VNode,
  container: RendererElement,
  anchor: RendererNode | null,
  parentComponent: ComponentInternalInstance | null,
  parentSuspense: SuspenseBoundary | null,
  isSVG: boolean,
  slotScopeIds: string[] | null,
  optimized: boolean
) => {
    // 1
    if (dirs) {
      invokeDirectiveHook(vnode, null, parentComponent, 'created')
    }
    // 2
    if (dirs) {
        invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
    }
    
    // 3
    queuePostRenderEffect(() => {
      vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
      needCallTransitionHooks && transition!.enter(el)
      dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
    }, parentSuspense)
}
const patchElement = (
  n1: VNode,
  n2: VNode,
  parentComponent: ComponentInternalInstance | null,
  parentSuspense: SuspenseBoundary | null,
  isSVG: boolean,
  slotScopeIds: string[] | null,
  optimized: boolean
) => {
  const el = (n2.el = n1.el!)
  
  // 1
  if (dirs) {
    invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate')
  }

  // 2
  queuePostRenderEffect(() => {
      vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, n2, n1)
      dirs && invokeDirectiveHook(n2, n1, parentComponent, 'updated')
    }, parentSuspense)
}
const unmount: UnmountFn = (
  vnode,
  parentComponent,
  parentSuspense,
  doRemove = false,
  optimized = false
) => {
  const {
    type,
    props,
    ref,
    children,
    dynamicChildren,
    shapeFlag,
    patchFlag,
    dirs
  } = vnode
  // unset ref
  if (ref != null) {
    setRef(ref, null, parentSuspense, vnode, true)
  }

  if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
    ;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)
    return
  }

  const shouldInvokeDirs = shapeFlag & ShapeFlags.ELEMENT && dirs

  let vnodeHook: VNodeHook | undefined | null
  if ((vnodeHook = props && props.onVnodeBeforeUnmount)) {
    invokeVNodeHook(vnodeHook, parentComponent, vnode)
  }

  if (shapeFlag & ShapeFlags.COMPONENT) {
    unmountComponent(vnode.component!, parentSuspense, doRemove)
  } else {


    if (shouldInvokeDirs) {
      invokeDirectiveHook(vnode, null, parentComponent, 'beforeUnmount')
    }

    queuePostRenderEffect(() => {
      vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode)
      shouldInvokeDirs &&
        invokeDirectiveHook(vnode, null, parentComponent, 'unmounted')
    }, parentSuspense)
}

在挂载元素VNode的时候,会调用指令的created, beforeMount和mounted钩子函数;

在更新元素VNode的时候,会调用指令的beforeUpdate, updated钩子函数;

在卸载元素VNode的时候,会调用指令的beforeUnmount, unmounted钩子函数;

关于指令的思考

组件上使用指令

我们上面提到了指令是作用在元素VNode上的,那组件使用指令(例如<son v-pin:[right]="20"></son>)是什么效果呢?结果是组件上使用的指令会作用在组件内部的根节点的元素VNode上。

export function renderComponentRoot(
  instance: ComponentInternalInstance
): VNode {
  const {
    type: Component,
    vnode,
    proxy,
    withProxy,
    props,
    propsOptions: [propsOptions],
    slots,
    attrs,
    emit,
    render,
    renderCache,
    data,
    setupState,
    ctx,
    inheritAttrs
  } = instance

    // inherit directives
    if (vnode.dirs) {
      if (__DEV__ && !isElementRoot(root)) {
        warn(
          `Runtime directive used on component with non-element root node. ` +
            `The directives will not function as intended.`
        )
      }
      root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs
    }
}

在组件渲染子树VNode的根VNode时候,会将组件的指令dirs添加在根元素VNode的dirs中。所以作用于组件的指令 等同于 作用于 根节点的元素VNode上。

组件上的一些使用场景

我觉得一些比较使用的指令的使用场景有:

  • v-lazyload: 图片的懒加载
  • v-loading:实现加一个加载动画
  • v-permission: 权限控制,没有权限就隐藏DOM元素
  • v-debounce: 输入防抖,特别是搜素框请求的输入

总结

到此这篇关于Vue3指令是如何实现的的文章就介绍到这了,更多相关Vue3指令实现内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • vue2.0实现的tab标签切换效果(内容可自定义)示例

    vue2.0实现的tab标签切换效果(内容可自定义)示例

    这篇文章主要介绍了vue2.0实现的tab标签切换效果,结合实例形式分析了vue.js实现内容可自定义的tab点击切换功能相关操作技巧,需要的朋友可以参考下
    2019-02-02
  • Vue中的MVVM模式使用及说明

    Vue中的MVVM模式使用及说明

    这篇文章主要介绍了Vue中的MVVM模式使用及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • vue中的inject用法及说明

    vue中的inject用法及说明

    这篇文章主要介绍了vue中的inject用法及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • vue3基础组件开发detePicker日期选择组件示例

    vue3基础组件开发detePicker日期选择组件示例

    这篇文章主要为大家介绍了vue3基础组件开发-detePicker(日期选择组件)示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • 深入理解Vue3里的EffectScope

    深入理解Vue3里的EffectScope

    本文主要介绍了Vue3里的EffectScope,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • vue项目本地开发完成后部署到服务器后报404错误解决方案

    vue项目本地开发完成后部署到服务器后报404错误解决方案

    很多时候我们发现辛辛苦苦写的VueJs应用经过打包后在自己本地搭建的服务器上测试没有什么问题,但真正放在服务器上后会发现或多或少的问题,这篇文章主要给大家介绍了关于vue项目本地开发完成后部署到服务器后报404错误的解决方案,需要的朋友可以参考下
    2024-01-01
  • 前端VUE双语实现方案详细教程

    前端VUE双语实现方案详细教程

    在项目需求中我们会遇到国际化的中英文切换,这篇文章主要给大家介绍了关于前端VUE双语实现方案的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-08-08
  • vue组件表单数据回显验证及提交的实例代码

    vue组件表单数据回显验证及提交的实例代码

    今天小编就为大家分享一篇vue组件表单数据回显验证及提交的实例代码,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08
  • echarts设置tootip轮播切换展示(vue3搭配vue-echarts粘贴即用)

    echarts设置tootip轮播切换展示(vue3搭配vue-echarts粘贴即用)

    这篇文章主要为大家介绍了echarts设置tootip轮播切换展示效果,vue3搭配vue-echarts粘贴即用,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2023-10-10
  • Vue3中当v-if和v-for同时使用时产生的问题和解决办法

    Vue3中当v-if和v-for同时使用时产生的问题和解决办法

    封装一个组件时,我使用到了v-for和v-if,它们在同一标签内,总是提示v-for循环出来的item在实例中没有被定义,查询资料后原因是因为v-for和v-if在同级使用时,v-if优先级比v-for高,所以本文给大家介绍了Vue3中当v-if和v-for同时使用时产生的问题和解决办法
    2024-07-07

最新评论