今天小编给大家分享一下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 }
指令搜寻
指令搜寻的时机
开发者是在模板中使用指令,所以应该是在模板渲染的时候需要先搜寻到对应的指令。
不使用指令的模板<h5>指令演示</h5>渲染函数如下:
function render(_ctx, _cache) { with (_ctx) { const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue return (_openBlock(), _createElementBlock("h5", null, "指令演示")) }}
使用指令的模板<h5 v-pin:[right]="20">指令演示</h5>渲染函数如下:
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("h5", null, _hoisted_2, 512 )), [ [_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指令是怎么实现的”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程网行业资讯频道。