v-show
和v-if
都是 Vue.js 框架里用于控制元素在页面上显示与隐藏的指令,它们存在以下一些共同点:
二者的核心功能都是基于特定条件来决定元素是否在页面中呈现给用户。借助表达式的布尔值(true
或false
),来控制元素显示或者隐藏的状态。例如下面的示例代码,通过不同按钮控制两个元素分别使用v-show
和v-if
来切换显示与隐藏:
这是用v-show控制的元素
这是用v-if控制的元素
当showFlag
和ifFlag
为true
时,对应的元素会显示;为false
时,元素则不显示。
它们都依赖于 Vue 实例的数据属性来动态控制元素的显示与隐藏。数据属性发生变化时,Vue 会自动更新 DOM,从而改变元素的显示状态。这与 Vue 响应式原理紧密相关,只要数据变化,对应的视图就会更新。
二者在使用时的语法基本相同,都是在 HTML 标签上以指令形式存在,并且后面跟一个 JavaScript 表达式。例如:
使用v-show
使用v-if
无论是v-show
还是v-if
,都能用于根据不同条件渲染不同的内容。在实际开发中,可根据业务逻辑设置不同的条件表达式,从而实现灵活的页面渲染。
核心区别
特性 | v-if | v-show |
---|---|---|
渲染机制 | 条件为真时渲染元素,否则销毁 | 始终渲染元素,通过 CSS 控制显示 |
DOM 操作 | 动态添加/移除 DOM 元素 | 仅切换 display: none 样式 |
初始开销 | 低(条件为假时不渲染) | 高(无论条件如何都渲染) |
切换开销 | 高(涉及组件销毁/重建) | 低(仅修改 CSS 属性) |
组件生命周期 | 触发 created /mounted 等钩子函数 |
不触发生命周期钩子(始终存在) |
组合使用 | 支持 v-else-if 和 v-else |
无逻辑分支语法 |
具体解析流程这里不展开讲,大致流程如下
template
转为ast
结构的JS
对象ast
得到的JS
对象拼装render
和staticRenderFns
函数render
和staticRenderFns
函数被调用后生成虚拟VNODE
节点,该节点包含创建DOM
节点所需信息vm.patch
函数通过虚拟DOM
算法利用VNODE
节点创建真实DOM
节点不管初始条件是什么,元素总是会被渲染
我们看一下在vue
中是如何实现的
代码很好理解,有transition
就执行transition
,没有就直接设置display
属性
// 引入 vShow 指令的类型定义,VShowElement 可能是自定义的元素类型
import { ObjectDirective } from 'vue';
// 定义 vShow 指令,它是一个对象指令,用于控制元素的显示与隐藏
// 这里指定了指令作用的元素类型为 VShowElement
export const vShow: ObjectDirective<VShowElement> = {
// beforeMount 钩子函数,在元素挂载到 DOM 之前调用
// el 是指令绑定的元素
// { value } 是指令绑定的值,即 v-show="value" 中的 value
// { transition } 是过渡效果相关的配置对象
beforeMount(el, { value }, { transition }) {
// 保存元素原本的 display 样式
// 如果元素原本的 display 为 'none',则将其存储为空字符串,否则存储当前的 display 值
el._vod = el.style.display === 'none' ? '' : el.style.display
// 如果存在过渡效果配置且 value 为 true
if (transition && value) {
// 调用过渡效果的 beforeEnter 钩子,准备元素进入过渡
transition.beforeEnter(el)
} else {
// 若没有过渡效果或者 value 为 false,直接设置元素的 display 属性
setDisplay(el, value)
}
},
// mounted 钩子函数,在元素挂载到 DOM 之后调用
mounted(el, { value }, { transition }) {
// 如果存在过渡效果配置且 value 为 true
if (transition && value) {
// 调用过渡效果的 enter 钩子,执行元素进入过渡动画
transition.enter(el)
}
},
// updated 钩子函数,在元素更新之后调用,这里省略了具体实现
updated(el, { value, oldValue }, { transition }) {
// ...
},
// beforeUnmount 钩子函数,在元素即将从 DOM 中移除之前调用
beforeUnmount(el, { value }) {
// 在元素卸载前,根据 value 值设置元素的 display 属性
setDisplay(el, value)
}
}
// 假设 setDisplay 函数用于根据 value 值设置元素的 display 属性
// 当 value 为 true 时显示元素,为 false 时隐藏元素
function setDisplay(el, value) {
el.style.display = value ? el._vod : 'none';
}
v-if
在实现上比v-show
要复杂的多,因为还有else
else-if
等条件需要处理,这里我们也只摘抄源码中处理 v-if
的一小部分
返回一个node
节点,render
函数通过表达式的值来决定是否生成DOM
// 从指定路径导入函数,该函数用于创建结构指令的转换函数
import { createStructuralDirectiveTransform } from '路径相关';
// 导入用于处理 v-if 节点生成代码节点的函数
import { processIf } from '路径相关';
// 导入用于为分支创建代码生成节点的函数
import { createCodegenNodeForBranch } from '路径相关';
// 导入用于获取父条件表达式的函数
import { getParentCondition } from '路径相关';
// 定义 transformIf 函数,用于转换 v-if、v-else 和 v-else-if 指令
export const transformIf = createStructuralDirectiveTransform(
// 正则表达式,用于匹配 v-if、v-else 和 v-else-if 指令
/^(if|else|else - if)$/,
// 回调函数,用于处理匹配到的指令节点
(node, dir, context) => {
// 调用 processIf 函数处理节点
return processIf(
// 指令绑定的节点
node,
// 指令对象
dir,
// 编译上下文
context,
// 处理分支的回调函数
(ifNode, branch, isRoot) => {
// 这里省略了部分代码,通常用于对分支进行预处理等操作
// ...
// 返回一个函数,该函数会在合适的时候被调用,用于生成代码节点
return () => {
// 判断当前分支是否为根分支
if (isRoot) {
// 如果是根分支,为该分支创建代码生成节点,并赋值给 ifNode 的 codegenNode 属性
ifNode.codegenNode = createCodegenNodeForBranch(
// 当前分支节点
branch,
// 分支的键,用于标识分支
key,
// 编译上下文
context
) as IfConditionalExpression;
} else {
// 如果不是根分支,将当前分支的代码生成节点附加到 v-if 根节点上
// 获取父条件表达式
const parentCondition = getParentCondition(ifNode.codegenNode!);
// 将当前分支的代码生成节点赋值给父条件表达式的 alternate 属性
parentCondition.alternate = createCodegenNodeForBranch(
// 当前分支节点
branch,
// 分支的键,根据根节点的分支数量进行计算
key + ifNode.branches.length - 1,
// 编译上下文
context
);
}
};
}
);
}
);
✅ 使用 v-if
的场景:
✅ 使用 v-show
的场景:
✅ 使用 v-if
的场景:
✅ 使用 v-show
的场景: