preface

Back to the classic sentence: “know what is, and then let it be.” You should be familiar with the use of V-if and V-show commands provided by Vue and the corresponding scenarios. However, I think there are still gaps in the knowledge of how V-IF and V-show instructions are implemented.

The implementation of v-if and V-show commands is very simple

v-if

Vue3 is a template that will go through baseParse, transform, and generate. Finally, generate generates executable code (the render function).

Here, we will not start from the compile process to explain the v-if command render function generation process, those who are interested in understanding this process, can read my previous article from the compile process, understand static node enhancement

We can enter a chestnut using the V-if instruction directly in Vue3 Template Explore:

<div v-if="visible"></div>
Copy the code

The render function that it compiles would then look like this:

render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_ctx.visible)
    ? (_openBlock(), _createBlock("div", { key: 0 }))
    : _createCommentVNode("v-if".true)}Copy the code

As you can see, a simple render function compiled using the template of the V-if directive ends up returning a ternary expression. First, let’s take a look at what some of these variables and functions mean:

  • _ctxThe context of the current component instance, i.ethis
  • _openBlock()_createBlock()Is used to constructBlock TreeBlock VNodeThey are mainly used to target the update process
  • _createCommentVNode()A function that creates comment nodes, usually used as placeholders

Obviously, an annotation node (also known as a placeholder node) is created in the current template if visible is false, and a real node (itself) is created if visible is false. For example, if visible is false it will render to the page like this:

Comment nodes are used as placeholder nodes in many places in Vue. The purpose is to identify the position of the element in the page when it is not displayed, so that the element can be put back to that position during patch.

So, at this point I think you have a question: what happens when visible dynamically toggles true or false?

Patch is sent to update nodes

For those of you who are not familiar with the process of distributing updates and dependency collection in Vue 3, you can read my previous article on the principles of 4K + Word analysis Vue 3.0 responsiveness (dependency Collection and dependency collection)

There are four instructions in Vue 3: V-ON, V-Model, V-show, and V-IF. However, in practice in the source code there are only special treatments for the first three, as shown in the packages/ Runtime -dom/ SRC /directives directory:

// packages/runtime-dom/src/directives| - driectives | -- vModel. Ts # # v - model instruction related | -- vOn. Ts # # v - instructions on relevant | -- vShow. Ts # # v - show commandsCopy the code

For V-IF instruction, patch logic is directly distributed in the update process. Since the V-IF instruction subscribed to visible variable, when visible changed, it would trigger distributing update, that is, the set logic of Proxy object, and finally hit the logic of componentEffect.

Of course, we can also call this process a component update process

Here, let’s look at the definition of componentEffect (pseudocode) :

// packages/runtime-core/src/renderer.ts
function componentEffect() {
    if(! instance.isMounted) { .... }else{...const nextTree = renderComponentRoot(instance)
        const prevTree = instance.subTree
        instance.subTree = nextTree
        patch(
          prevTree,
          nextTree,
          hostParentNode(prevTree.el!)!,
          getNextHostNode(prevTree),
          instance,
          parentSuspense,
          isSVG
        )
        ...
      }
  }
}
Copy the code

As you can see, when the component is not yet mounted, i.e. the first time to trigger the distribution update will hit! Instance. isMounted logic For our case, we hit the else logic, that is, component updates, which do three main things:

  • Gets the component tree for the current componentnextTreeAnd the component tree before thatprevTree
  • Update the current component instanceinstanceThe components of the treesubTreenextTree
  • patchOld and new component treeprevTreenextTree, if there isdynamicChildren, i.e.,Block Tree, will hit the logic of targeted update, obviously we meet the condition at this time

Note: Component Tree refers to the VNode Tree corresponding to the component.

summary

In general, the implementation of THE V-IF instruction is relatively simple. Based on the data-driven concept, when the value corresponding to the V-IF instruction is false, an annotation node will be created in advance at this position. Then, when the value changes, the logic of issuing updates will be matched to patch the old and new component trees. This completes the dynamic display hide of elements using the V-if instruction.

Let’s look at the implementation of the V-show instruction

v-show

Similarly, for the V-show directive, we enter a chestnut on the Vue 3 online template compilation platform:

<div v-show="visible"></div>
Copy the code

So, the render function generated by it compiles:

render(_ctx, _cache, $props, $setup, $data, $options) {
  return _withDirectives((_openBlock(), _createBlock("div".null.null.512 /* NEED_PATCH */)), 
  [
    [_vShow, _ctx.visible]
  ])
}
Copy the code

At this point, the chestnut renders to the HTML on the page when visible is false:

As shown by the above render function, unlike the V-if triscache operator expression, the V-show render function returns the execution of the _withDirectives() function.

Previously, we briefly introduced the _openBlock() and _createBlock() functions. So, let’s take a look at the render function point by point, starting with _vShow ~

VShow changes the display property during its lifetime

_vShow in the source corresponds to vShow, which is defined in Packages/Runtime -dom/ SRC /directives/vShow. BeforeMount, Mounted, updated, and beforeUnMount are the four life cycles in which v-show commands are processed specially:

// packages/runtime-dom/src/directives/vShow.ts
export const vShow: ObjectDirective<VShowElement> = {
  beforeMount(el, { value }, { transition }) {
    el._vod = el.style.display === 'none' ? ' ' : el.style.display
    if (transition && value) {
      // Process the tansition logic. }else {
      setDisplay(el, value)
    }
  },
  mounted(el, { value }, { transition }) {
    if (transition && value) {
      // Process the tansition logic. }},updated(el, { value, oldValue }, { transition }) {
    if(! value === ! oldValue)return
    if (transition) {
      // Process the tansition logic. }else {
      setDisplay(el, value)
    }
  },
  beforeUnmount(el, { value }) {
    setDisplay(el, value)
  }
}
Copy the code

The V-show command handles two pieces of logic: regular V-show or transition V-show. Usually we just use the V-show command and hit the former.

Here we only analyze the normal V-show case.

In the normal V-show case, the setDisplay() function is called and two variables are passed in:

  • elThe current use ofv-showThe directiveReal elements
  • v-showInstruction correspondingvalueThe value of the

Next, let’s look at the definition of the setDisplay() function:

function setDisplay(el: VShowElement, value: unknown) :void {
  el.style.display = value ? el._vod : 'none'
}
Copy the code

The setDisplay() function, as its name implies, dynamically controls the display or hiding of the v-show bound element by changing the value of the element’s CSS property display.

Also, I think you may have noticed that when value is true, display is equal to el.vod, and el.vod is equal to the real element’s CSS display property (which is empty by default). Therefore, when the value of v-show is true, the display of the element depends on its CSS display property.

In fact, the essence of the V-show instruction in the source code has come out. However, there are still some questions such as what do the withDirectives do? How does vShow handle the V-show instructions in its life cycle?

WithDirectives Adds the dir attribute on the VNode

WithDirectives (), as the name implies, is directives related, that is, for the elements that are directives related in Vue 3, the last render function generated will call withDirectives() to handle the directives related logic, Add vShow logic to VNode as dir property.

Definition of the withDirectives() function:

// packages/runtime-core/src/directives.ts
export function withDirectives<T extends VNode> (vnode: T, directives: DirectiveArguments) :T {
  const internalInstance = currentRenderingInstance
  if (internalInstance === null) {
    __DEV__ && warn(`withDirectives can only be used inside render functions.`)
    return vnode
  }
  const instance = internalInstance.proxy
  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)) {
      ...
    }
    bindings.push({
      dir,
      instance,
      value,
      oldValue: void 0,
      arg,
      modifiers
    })
  }
  return vnode
}
Copy the code

First, withDirectives() will fetch the current render instance to handle edge conditions that will throw an exception if used outside of the render function:

“withDirectives can only be used inside render functions.”

Then bind the DIRS property on vNode and walk through the array of directives passed in, which for our chestnut cache is:

[
  [_vShow, _ctx.visible]
]
Copy the code

Obviously, you only iterate once (array length 1). And from the parameters passed in to Render it is known that the dir deconstructed from the Directives refers to _vShow which we described above. Since vShow is an object, a (binding.push ()) dir is reconstructed to vnode.dir.

Vnode. dir is useful when vShow changes the CSS display property of elements during life cycles that are called as end-of-update callbacks.

Next, let’s look at the call details ~

Patch, register when distributing updatepostRenderEffectThe event

I believe you all know that Vue 3 puts forward the concept of patchFlag, which is used to execute corresponding patch logic for different scenarios. So, for the chestnut above, we will hit the patchElement logic.

For directives such as v-show, the logic that handles the CSS display property of the element is bound to vnode. dir (the lifecycle handling defined by vShow). So, a postRenderEffect event is registered in patchElement().

// packages/runtime-core/src/renderer.ts
const patchElement = (
    n1: VNode,
    n2: VNode,
    parentComponent: ComponentInternalInstance | null,
    parentSuspense: SuspenseBoundary | null,
    isSVG: boolean,
    optimized: boolean
  ) = >{...// Dirs exists
    if ((vnodeHook = newProps.onVnodeUpdated) || dirs) {
      // Register postRenderEffect events
      queuePostRenderEffect(() = > {
        vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, n2, n1)
        dirs && invokeDirectiveHook(n2, n1, parentComponent, 'updated')
      }, parentSuspense)
    }
    ...
  }
Copy the code

Here’s a quick look at queuePostRenderEffect() and invokeDirectiveHook() :

  • QueuePostRenderEffect (), postRenderEffect event registration is done with the queuePostRenderEffect() function, since effects are maintained in a queue (to keep them in order), This is pendingPostFlushCbs, so the same thing for postRenderEffect will be in the team

  • InvokeDirectiveHook (), because vShow encapsulates the handling of the element’s CSS display property, invokeDirective() is designed to invoke instruction-related lifecycle handling. Also, note that this is the update logic, so only the Update life cycle defined in vShow will be called

The closing (finally) call to flushJobspostRenderEffect

Here we have covered the concepts of vShow, withDirectives, postRenderEffect, and so on around the V-show. However, everything is in short supply and there is still a time to call the postRenderEffect event, which is the time to process the pendingPostFlushCbs queue.

In Vue 3 effect is equivalent to watch in Vue 2.x. The name changes, but the call remains the same: the run() function is called and the effect queue is executed by flushJobs(). The time to call postRenderEffect events is at the end of the execution queue.

FlushJobs () ¶

// packages/runtime-core/src/scheduler.ts
function flushJobs(seen? : CountMap) {
  isFlushPending = false
  isFlushing = true
  if (__DEV__) {
    seen = seen || new Map()
  }
  flushPreFlushCbs(seen)
  // Sort effect
  queue.sort((a, b) = >getId(a!) - getId(b!) )try {
    for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
      // Execute render effect
      const job = queue[flushIndex]
      if(job) { ... }}}finally{...// postRenderEffect execution time of the eventflushPostFlushCbs(seen) ... }}Copy the code

FlushJobs () executes three effect queues, preRenderEffect, renderEffect, postRenderEffect, They correspond to flushPreFlushCbs(), Queue, and flushPostFlushCbs.

So, obviously the postRenderEffect event is called at flushPostFlushCbs(). The pendingPostFlushCbs queue is iterated inside flushPostFlushCbs(), which executes postRenderEffect events previously registered at patchElement, essentially executing:

updated(el, { value, oldValue }, { transition }) {
  if(! value === ! oldValue)return
  if (transition) {
    ...
  } else {
    // Change the element's CSS display attribute
    setDisplay(el, value)
  }
},
Copy the code

summary

Compared with V-IF, which simply updates elements directly through patch, V-show is a little more complicated. Here’s a recap of the process:

  • First of all, bywidthDirectivesTo generate the finalVNode. It will giveVNodeThe bindingdirAttributes, i.e.,vShowDefines CSS for elements in the life cycledisplayHandling of attributes
  • Secondly, in thepatchElementPhase, will registerpostRenderEffectEvent for invokingvShowThe definition of theupdateThe life cycle processes the CSSdisplayLogic of attributes
  • Finally, at the end of distributing updates, callpostRenderEffectEvent, that is, executionvShowThe definition of theupdateLife cycle, changes the ELEMENT’s CSSdisplayattribute

conclusion

V-if and V-Show are implemented in a way that you can summarize in a sentence or two, or a whole bunch of words. When it comes to interview situations, I prefer the latter because it shows that you have researched deeply and understood well. In addition, when you understand the processing process of one instruction, it is easy to draw conclusions about the processing of other instructions v-ON and V-Model. Finally, if there is any improper expression or mistake in the article, please make an Issue

I am Wu Liu, like innovation, tamping source Code, focus on Vue3 source Code, Vite source Code, front-end engineering and other technology sharing, welcome to pay attention to my wechat public number: Code Center.