v-show 和 v-if 的底层实现

477 阅读2分钟

之前面试突然被问到,我觉得大家几乎都能答出来个大概,但我还是决定看一下源码吧~ (基于 vue3)

从 render 函数观察

大家应该都知道 vue3 官方文档提供了一个演练场吧?可以直观的看到组件编译后的结果。
我在 sfc.vuejs.org/ 添加两句代码:

<script setup>
import { ref } from 'vue'

const msg = ref('Hello World!')
const isShow = ref(true)
</script>

<template>
  <div v-if="isShow">{{ msg }}</div>
  <div v-show="isShow">{{ msg + '111111' }}</div>
  <h1>{{ msg }}</h1>
  <input v-model="msg">
</template>

编译的 render 代码如下,很明显能看到根据 isShow 的值做了什么处理:

const _hoisted_1 = { key: 0 }

return (_ctx, _cache) => {
  return (_openBlock(), _createElementBlock(_Fragment, null, [
    (isShow.value)
      ? (_openBlock(), _createElementBlock("div", _hoisted_1, _toDisplayString(msg.value), 1 /* TEXT */))
      : _createCommentVNode("v-if", true),
    _withDirectives(_createElementVNode("div", null, _toDisplayString(msg.value + '111111'), 513 /* TEXT, NEED_PATCH */), [
      [_vShow, isShow.value]
    ]),
    _createElementVNode("h1", null, _toDisplayString(msg.value), 1 /* TEXT */),
    _withDirectives(_createElementVNode("input", {
      "onUpdate:modelValue": _cache[0] || (_cache[0] = $event => ((msg).value = $event))
    }, null, 512 /* NEED_PATCH */), [
      [_vModelText, msg.value]
    ])
  ], 64 /* STABLE_FRAGMENT */))
}

那么就开始看看源码吧~

v-show

基本流程:通过 createElementVNode 创建一个 vNode,然后再通过 withDirectives 将 vue 的本身对 vShow 特殊指令的处理把指令加到 vNode 上,最后返回最终的 vNode

withDirectives

在 runtime-dom/src/directives 文件夹,有 vue 对内置指令 v-model、 v-on、 v-show 的 实现(withDirectives)

image.png withDirectives 最终返回的是一个 vnode

vShow

vShow.ts 主要是 vue 针对 v-show 指令做一些处理:在生命周期 beforeMount、mounted、updated、beforeUnmount 都做了不同的处理(普通情况 和 transition 情况),最终主要实现还是 setDisplay 方法

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

如此看到,底层实现主要还是通过对该元素的 css 属性 display 的设置来进行显隐的

v-if

值为 false

_createCommentVNode("v-if", true)

可以看到传入的 text 参数为 v-if,asBlock 设置为 true

image.png

  • openBlock() :打开一个块。这必须在createBlock之前调用。它不能是createBlock的一部分,因为该块的子级额能自己调用createBlock。当创建 v-for fragment 块时,disableTracking 为 true,因为 v-for fragment 总是对其子级进行 diff。

  • createBlock() :创建块根 vnode。采用与 createVNode 相同的参数。块根跟踪 dynamicChildren 数组中块内的动态节点。

    所以这里创建一个块,最终渲染成注释,内容是 v-if

image.png

值为 true

(_openBlock(), _createElementBlock("div", _hoisted_1, _toDisplayString(msg.value)) 创建一个块,放入后面创建的块元素 div

_hoisted_1: { key: 0 }:hoist提升的意思,试了一下,应该只是用 key 来标识不同节点的

image.png _toDisplayString(msg.value):将 msg 的值转为 string 类型

综上, v-if 主要是通过值来判断创建节点的。

总结

image.png