v-if
在之前模版编译一节中,我给大家介绍了 Vue 3 的编译过程,即一个模版会经历 baseParse
、transform
、generate
这三个过程,最后由 generate
生成可以执行的代码(render
函数)。
这里,我们就不从编译过程开始讲解
v-if
指令的render
函数生成过程了,有兴趣了解这个过程的同学,可以看我之前的模版编译一节
我们可以直接在 Vue3 Template Explore 输入一个使用 v-if
指令的栗子:
<div v-if="visible"></div>
然后,由它编译生成的 render
函数会是这样:
render(_ctx, _cache, $props, $setup, $data, $options) {
return (_ctx.visible)
? (_openBlock(), _createBlock("div", { key: 0 }))
: _createCommentVNode("v-if", true)
}
可以看到,一个简单的使用 v-if
指令的模版编译生成的 render
函数最终会返回一个三目运算表达式。首先,让我们先来认识一下其中几个变量和函数的意义:
_ctx
当前组件实例的上下文,即this
_openBlock()
和_createBlock()
用于构造Block Tree
和Block VNode
,它们主要用于靶向更新过程_createCommentVNode()
创建注释节点的函数,通常用于占位
显然,如果当 visible
为 false
的时候,会在当前模版中创建一个注释节点(也可称为占位节点),反之则创建一个真实节点(即它自己)。例如当 visible
为 false
时渲染到页面上会是这样:
在 Vue 中很多地方都运用了注释节点来作为占位节点,其目的是在不展示该元素的时候,标识其在页面中的位置,以便在
patch
的时候将该元素放回该位置。
那么,这个时候我想大家就会抛出一个疑问:当 visible
动态切换 true
或 false
的这个过程(派发更新)究竟发生了什么?
派发更新时 patch,更新节点
如果不了解 Vue 3 派发更新和依赖收集过程的同学,可以看我之前的文章4k+ 字分析 Vue 3.0 响应式原理(依赖收集和派发更新)
在 Vue 3 中总共有四种指令:v-on
、v-model
、v-show
和 v-if
。但是,实际上在源码中,只针对前面三者进行了特殊处理,这可以在 packages/runtime-dom/src/directives
目录下的文件看出:
// packages/runtime-dom/src/directives
|-- driectives
|-- vModel.ts ## v-model 指令相关
|-- vOn.ts ## v-on 指令相关
|-- vShow.ts ## v-show 指令相关
而针对 v-if
指令是直接走派发更新过程时 patch
的逻辑。由于 v-if
指令订阅了 visible
变量,所以当 visible
变化的时候,则会触发派发更新,即 Proxy
对象的 set
逻辑,最后会命中 componentEffect
的逻辑。
当然,我们也可以称这个过程为组件的更新过程
这里,我们来看一下 componentEffect
的定义(伪代码):
// 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
)
...
}
}
}
可以看到,当组件还没挂载时,即第一次触发派发更新会命中 !instance.isMounted
的逻辑。而对于我们这个栗子,则会命中 else
的逻辑,即组件更新,主要会做三件事:
- 获取当前组件对应的组件树
nextTree
和之前的组件树prevTree
- 更新当前组件实例
instance
的组件树subTree
为nextTree
patch
新旧组件树prevTree
和nextTree
,如果存在dynamicChildren
,即Block Tree
,则会命中靶向更新的逻辑,显然我们此时满足条件
注:组件树则指的是该组件对应的 VNode Tree。
总结
总体来看,v-if
指令的实现较为简单,基于数据驱动的理念,当 v-if
指令对应的 value
为 false
的时候会预先创建一个注释节点在该位置,然后在 value
发生变化时,命中派发更新的逻辑,对新旧组件树进行 patch
,从而完成使用 v-if
指令元素的动态显示隐藏。
那么,下一节,我们来看一下
v-show
指令的实现~