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

目录
  • 前言
  • 指令注册
    • 全局注册
    • 组件内注册
  • 指令搜寻
    • 指令搜寻的时机
    • 指令搜寻的逻辑
  • 指令绑定VNode
  • 指令调用
  • 关于指令的思考
    • 组件上使用指令
    • 组件上的一些使用场景
  • 总结

前言

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指令实现内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Vue3.0写自定义指令的简单步骤记录

    前言 vue中提供了丰富的内置指令,如v-if,v-bind,v-on......,除此之外我们还可以通过Vue.directive({})或者directives:{}来定义指令 在开始学习之前我们应该理解,自定义指令的应用场景,任何功能的开发都是为了解决具体的问题的, 通过自定义指令,我们可以对DOM进行更多的底层操作,这样不仅可以在某些场景下为我们提供快速解决问题的思路,而且让我们对vue的底层有了进一步的了解 第一步 在main.js 在src下简历对应的文件夹 import Direc

  • vue3.0自定义指令(drectives)知识点总结

    在大多数情况下,你都可以操作数据来修改视图,或者反之.但是还是避免不了偶尔要操作原生 DOM,这时候,你就能用到自定义指令. 举个例子,你想让页面的文本框自动聚焦,在没有学习自定义指令的时候,我们可能会这么做. const app = Vue.createApp({ mounted(){ this.$refs.input.focus(); }, template: `<input type="text" ref="input" />`, }); 在mou

  • vue3 自定义指令详情

    目录 一.注册自定义指令 1.1.全局自定义指令 1.2.局部自定义指令 二.自定义指令中的生命周期钩子函数 三.自定义指令钩子函数的参数 四.自定义指令参数 一.注册自定义指令 以下实例都是实现一个输入框自动获取焦点的自定义指令. 1.1.全局自定义指令 在vue2中,全局自定义指令通过 directive 挂载到 Vue 对象上,使用 Vue.directive('name',opt). 实例1:Vue2 全局自定义指令 Vue.directive('focus',{ inserted:(e

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

    目录 前言 指令注册 全局注册 组件内注册 指令搜寻 指令搜寻的时机 指令搜寻的逻辑 指令绑定VNode 指令调用 关于指令的思考 组件上使用指令 组件上的一些使用场景 总结 前言 Vue 指令 是指 对普通DOM元素进行底层操作的JS对象, 它们会被挂在Element VNode对象上,在Element VNode的一些生命周期中会被调用,从而可以操作Element VNode的底层DOM元素. 指令注册 指令注册 是指将指令对应的JS代码放置在某些地方,需要使用的时候可以在这些地方进行查找.

  • 一篇文章搞懂Vue3中如何使用ref获取元素节点

    目录 前言 1.回顾 Vue2 中的 ref 2.Vue3 中 ref 访问元素 3.v-for 中使用 ref 4.ref 绑定函数 5.组件上使用 ref 总结 前言 虽然在 Vue 中不提倡我们直接操作 DOM,毕竟 Vue 的理念是以数据驱动视图.但是在实际情况中,我们有很多需求都是需要直接操作 DOM 节点的,这个时候 Vue 提供了一种方式让我们可以获取 DOM 节点:ref 属性.ref 属性是 Vue2 和 Vue3 中都有的,但是使用方式却不大一样,这也导致了很多从 Vue2

  • 一篇文章告诉你如何编写Vue插件

    目录 什么是插件 编写插件 使用插件 总结 什么是插件 在Vue框架中,如果需要给Vue增加一些我们需要的功能,Vue给我留了一个插件的方式,我们可以编写自己的插件,然后在Vue中去注册插件,然后去使用他. 通过Vue插件我们可以实现一些Vue框架没有的功能,也可以使用别人写好的插件,来帮助我们更快速的实现一些功能. 插件的功能范围并没有严格的要求,根据官方的示例来说,一般有下面几种: 1.添加全局方法或者属性,如:vue-custom-element,我们可以用插件方式添加一些全局组件,然后可

  • 一篇文章告诉你JAVA Mybatis框架的核心原理到底有多重要

    目录 持久层的那些事 什么是 JDBC JDBC 原理 什么是 Mybatis Mybatis 与 JDBC 的关系 SqlSession SqlSessionFactory SqlSessionFactoryBuilder Configuration MappedStatement Executor ParameterHandler StatementHandler ResultSetHandler Interceptor Mybatis 关键词说明 Mybatis 架构设计 基础支持层 反射

  • 一篇文章告诉你如何用python进行自动化测试,调用c程序

    目录 一.介绍 二.方法 三.示例 windows平台 1.编写搭建动态库c函数 2.python导入库 3.结果 linux平台 1.编写c程序 2.编译成so动态库 3.编写python导入库 4.执行 总结 一.介绍 python可以做测试c的程序, 用到ctypes模块 ctypes 有以下优点: Python内建,不需要单独安装 可以直接调用二进制的动态链接库 在Python一侧,不需要了解Python内部的工作方式 在C/C++一侧,也不需要了解Python内部的工作方式 对基本类型

  • 一篇文章告诉你如何用Python控制Excel实现自动化办公

    目录 1.安装 2.操作一个简单的Excel文档 3. 操作简单Excel文档并添加数据格式 4.Excel中添加不同类型的数据 5.Excel中添加数据图表 最后:可能给予你助力的教程 总结 1.安装 2.操作一个简单的Excel文档 操作注释及代码: 操作完成后,数据存储结果如下: 3. 操作简单Excel文档并添加数据格式 操作代码如下: 附带数据格式的定义 操作效果如图所示: 4.Excel中添加不同类型的数据 操作代码如下: 将不同的数据按照指定的格式添加到文件中 代码执行结果如下:

  • 一篇文章告诉你如何在Java数组中插入一个字符

    目录 定义一个数组 定义插入的字符 打印插入之前字符排列顺序 假设插入位置 找到插入位置 数组数据下移 移入数值 输出数组 总结 定义一个数组 public class charInsert { public static void main(String[] args) { // 这是字符数组 char[] ch = new char[9]; ch[0] = 'a'; ch[1] = 'b'; ch[2] = 'c'; ch[3] = 'f'; ch[4] = 'g'; ch[5] = 'i'

  • 一篇文章告诉你如何实现Vue前端分页和后端分页

    目录 1:前端手写分页(数据量小的情况下) 2:后端分页,前端只需要关注传递的page和pageSize 总结 1:前端手写分页(数据量小的情况下) 前端需要使用slice截取: tableData((page-1)pageSize,pagepageSize) 2:后端分页,前端只需要关注传递的page和pageSize 3:前端手写分页按钮 <body> <div id="app"> <table class="table table-bord

  • 一篇文章告诉你如何用事件委托实现JavaScript留言板功能

    用事件委托实现留言板功能. <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width

  • 一篇文章告诉你JavaScript的作用域和函数该这样理解

    目录 一.作用域 1.1 局部作用域 1.函数作用域 2.块作用域 1.2 全局作用域 1.3 作用域链 1.4.闭包 1.5 变量提升 二.函数 2.1.函数提升 2.2.函数参数 1.默认参数 2.动态参数 3.剩余参数 2.3.箭头函数 总结 一.作用域 [解释]: 规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问. [分类]: 局部作用域全局作用域 1.1 局部作用域 [分类]: 数作用域块作用域 1.函数作用域 [解释]: 在函数内部声明的变量只能在函数内部被访问,外

随机推荐