在 Vue 开发中,v-if
、v-show
、v-html
是我们每天都在使用的指令。
但你是否思考过:
"为什么
v-if
切换时组件会重新渲染,而v-show
不会?"
"v-html
真的安全吗?它底层是怎么工作的?"
本文将带你深入 Vue 源码,从编译到渲染 ,彻底解析这三大指令的工作原理与性能差异。
一、v-if:条件渲染的"销毁与重建"大师
✅ 行为表现
vue
<div v-if="isVisible">我是条件内容</div>
isVisible = true
:元素存在 DOM 中;isVisible = false
:元素从 DOM 中完全移除。
📌 编译阶段:生成条件 AST 节点
当 Vue 编译器解析模板时:
- 遇到
v-if
,调用addIfCondition
方法; - 将该节点标记为"条件节点";
- 生成抽象语法树(AST)时,为该节点添加
if
和ifConditions
属性。
js
// 编译后的 AST 片段
{
tag: 'div',
if: 'isVisible',
ifConditions: [
{
exp: 'isVisible',
block: { /* 节点本身 */ }
}
]
}
📌 渲染阶段:动态生成 VNode
在 render
函数执行时:
- 如果条件为
false
,不生成该节点的 VNode; - 如果为
true
,生成 VNode 并参与后续 diff。
js
// render 函数类似
render(h) {
return this.isVisible
? h('div', '我是条件内容')
: undefined; // 不渲染
}
✅ 生命周期影响
- 销毁时 :触发
beforeDestroy
→destroyed
; - 重建时 :重新执行
created
→mounted
; - 子组件状态丢失。
📌 适用场景
- 条件很少改变;
- 切换开销大,但初始渲染性能敏感;
- 需要完全隔离组件实例。
二、v-show:CSS 控制的"显示开关"
✅ 行为表现
vue
<div v-show="isVisible">我是显示控制内容</div>
isVisible = true
:display: block
(或其他);isVisible = false
:display: none
。
📌 编译阶段:生成普通 VNode
与 v-if
不同,v-show
:
- 不会被标记为条件节点;
- 正常生成 VNode,无论条件真假;
- 编译器为其添加一个
directives
属性:
js
{
tag: 'div',
directives: [
{
name: 'show',
value: 'isVisible',
rawName: 'v-show'
}
],
children: [ /* 文本节点 */ ]
}
📌 渲染阶段:通过 CSS 控制显示
在 patch
阶段,Vue 会执行 v-show
的钩子函数:
js
// 源码简化
function updateShow (oldVnode, vnode) {
const show = vnode.data.directives.find(d => d.name === 'show').value;
vnode.elm.style.display = show ? '' : 'none';
}
- ✅ 始终渲染:VNode 始终存在;
- ✅ 仅修改
display
:DOM 节点不销毁; - ✅ 保留组件状态 :
data
、事件监听器、子组件均保留。
✅ 生命周期影响
- ❌ 不触发
destroyed
或created
; - 组件始终处于"活跃"状态。
📌 适用场景
- 频繁切换显示状态;
- 需要保留组件内部状态;
- 初始渲染性能不敏感。
三、v-html:HTML 内容注入的"双刃剑"
✅ 行为表现
vue
<div v-html="htmlContent"></div>
js
data() {
return {
htmlContent: '<span style="color:red">红色文本</span>'
};
}
- 结果:
<div><span style="color:red">红色文本</span></div>
📌 编译阶段:标记为 HTML 指令
- 编译器识别
v-html
; - 生成 AST 时,添加
directives
属性:
js
{
tag: 'div',
directives: [
{
name: 'html',
value: 'htmlContent',
rawName: 'v-html'
}
]
}
- 移除原有子节点(如文本、其他元素)。
📌 渲染阶段:直接设置 innerHTML
在 patch
过程中,Vue 调用 addProp
为元素添加 innerHTML
属性:
js
// 源码简化
function updateHtml (oldVnode, vnode) {
const html = vnode.data.directives.find(d => d.name === 'html').value;
if (vnode.elm.innerHTML !== html) {
vnode.elm.innerHTML = html;
}
}
- ✅ 性能高 :浏览器原生
innerHTML
操作; - ❌ 不安全 :可能引发 XSS 攻击;
- ❌ 不响应式 :如果
htmlContent
变化,会完全替换内部内容,而非 diff。
⚠️ 安全警告
js
// 危险!用户输入可能包含恶意脚本
this.htmlContent = '<img src=x onerror="alert(\'XSS\')">';
// ✅ 正确做法:使用插值或文本指令
<div>{{ userContent }}</div>
📌 适用场景
- 渲染可信的富文本(如 CMS 内容);
- 性能要求极高,且内容简单;
- 配合
DOMPurify
等库进行内容净化。
四、三大指令对比表
特性 | v-if |
v-show |
v-html |
---|---|---|---|
是否生成 VNode | 条件生成 | 始终生成 | 始终生成 |
DOM 操作 | 添加/移除节点 | 修改 display |
设置 innerHTML |
初始渲染性能 | ✅ 更好(条件为假) | ❌ 较差 | ✅ 快(但风险高) |
切换性能 | ❌ 差(重建组件) | ✅ 好(仅 CSS) | ✅ 快(但全替换) |
保留状态 | ❌ 否 | ✅ 是 | ❌ 否(内容被替换) |
安全性 | ✅ 安全 | ✅ 安全 | ❌ 可能 XSS |
适用场景 | 条件少变 | 频繁切换 | 可信富文本 |
五、性能优化建议
✅ v-if
vs v-show
如何选?
条件 | 推荐 |
---|---|
切换频率 < 3次/分钟 | v-if |
切换频率 > 3次/分钟 | v-show |
组件包含大量子节点 | v-show (避免重复 diff) |
初始加载需隐藏 | v-if (减少首次渲染) |
✅ v-html
安全使用指南
- 永远不要 将用户输入直接用于
v-html
; - 使用
DOMPurify
净化 HTML:
js
import DOMPurify from 'dompurify';
const clean = DOMPurify.sanitize(dirtyHTML);
this.htmlContent = clean;
- 考虑使用
v-dom
(第三方指令)或dangerouslySetInnerHTML
(Vue 3 Composition API)替代。
💡 结语
"理解原理,才能写出高性能、安全的代码。"
v-if
:条件渲染,走的是"销毁-重建"路线;v-show
:显示控制 ,靠display: none
隐藏;v-html
:HTML 注入 ,直接设置innerHTML
,快但危险。
掌握它们的底层机制,你就能:
✅ 避免不必要的组件重建;
✅ 提升频繁切换的性能;
✅ 防止 XSS 安全漏洞。