Vue directives are JS objects that perform low-level operations on ordinary DOM elements. They are attached to Element VNode objects and are called during some Element VNode lifecycle to manipulate Element VNode’s underlying DOM elements.

Instructions to register

Instruction registration refers to placing the JS code corresponding to an instruction in some places, where it can be searched when needed.

Global registration

  • Global registration is calledApp. directive(' directive name ', {directive code})To implement the
app.directive('pin', (el, binding) => {
  el.style.position = 'fixed'
  const s = binding.arg || 'top'
  el.style[s] = binding.value + 'px'
})
Copy the code
  • The logic of global registration is to mount the instruction name and corresponding instruction code globallycontextthedirectivesOn the object
<! -- apiCreateApp.js --> directive(name: string, directive? Directives [name] = Directive return app} : directives) {// Mount to the global 'context'. Cache [name] = Directive return app}Copy the code

In-component registration

  • In-component registration is an option to add directives to the component
directives: {
  pin: (el, binding) => {
    el.style.position = 'fixed'
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  }
}
Copy the code
  • The logic of component registration is to mount the directive name and corresponding directive code on the component instance objectdirectiveson
<! -- component.ts --> export function applyOptions(instance: ComponentInternalInstance) {/ / mounted in the component instance object ` directives on ` instance. The directives = directives}Copy the code

Instructions for

The timing of the search

The developer is using the commands in the template, so it is necessary to search for them before rendering the template.

function render(_ctx, _cache) { with (_ctx) { const { openBlock: _openBlock, createElementBlock: _createElementBlock} = _Vue return (_openBlock(), _createElementBlock("h4", null, "instruction demo "))}}Copy the code

Using the template

instruction demo

render function as follows:

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]
    ])
  }
}

Copy the code

Templates that use directives need to search for the corresponding directives and bind them to vNodes

Command search logic

  • The logic of the command search is to start with the component instanceinstancethedirectivesIf you don’t find it, try againappContextthedirectivesOn looking for
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
}
Copy the code

The directive binds 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
}
Copy the code

Mount each instruction dir and some other parameters on the VNode dirs. Other parameters are: the instance component instance, the new value of the value directive (20 in this case), the oldValue of the oldValue directive (0 in this case), and the argument to the arg directive (right in this case)

The instruction calls

Instruction invocation refers to when the instruction code is executed. We initially mentioned that directives are JS objects that perform low-level operations on ordinary DOM elements, so the logic for directives should be handled in 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) }Copy the code
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)
}
Copy the code
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) }Copy the code
  1. Mounting elementsVNodeIs calledThe directivecreated.beforeMountandmountedHook function;
  2. Updating elementsVNodeIs calledThe directivebeforeUpdate.updatedHook function;
  3. Unloading elementsVNodeIs calledThe directivebeforeUnmount.unmountedHook function;

Thinking about instructions

Use directives on components

The result is that the instructions used on the component are applied to the element VNode of the root node inside the component.

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 } }Copy the code

When a component renders the root VNode of the subtree VNode, the component’s instruction dirs is added to the root VNode’s dirs. So directives that apply to components are equivalent to directives that apply to the root node element VNode.

Some usage scenarios on components

I think some of the most useful commands are:

  • V-lazyload: Lazy loading of images
  • V-loading: Implementation plus a loading animation
  • V-permission: Permission control that hides DOM elements without permission
  • V-debounce: Input anti-shock, especially as requested by the search box