【vue篇】Vue 核心指令原理解析:v-if、v-show、v-html 的底层奥秘

在 Vue 开发中,v-ifv-showv-html 是我们每天都在使用的指令。

但你是否思考过:

"为什么 v-if 切换时组件会重新渲染,而 v-show 不会?"
"v-html 真的安全吗?它底层是怎么工作的?"

本文将带你深入 Vue 源码,从编译到渲染 ,彻底解析这三大指令的工作原理与性能差异


一、v-if:条件渲染的"销毁与重建"大师

✅ 行为表现

vue 复制代码
<div v-if="isVisible">我是条件内容</div>
  • isVisible = true:元素存在 DOM 中;
  • isVisible = false元素从 DOM 中完全移除

📌 编译阶段:生成条件 AST 节点

当 Vue 编译器解析模板时:

  1. 遇到 v-if,调用 addIfCondition 方法;
  2. 将该节点标记为"条件节点";
  3. 生成抽象语法树(AST)时,为该节点添加 ififConditions 属性。
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; // 不渲染
}

✅ 生命周期影响

  • 销毁时 :触发 beforeDestroydestroyed
  • 重建时 :重新执行 createdmounted
  • 子组件状态丢失

📌 适用场景

  • 条件很少改变;
  • 切换开销大,但初始渲染性能敏感;
  • 需要完全隔离组件实例。

二、v-show:CSS 控制的"显示开关"

✅ 行为表现

vue 复制代码
<div v-show="isVisible">我是显示控制内容</div>
  • isVisible = truedisplay: block(或其他);
  • isVisible = falsedisplay: none

📌 编译阶段:生成普通 VNode

v-if 不同,v-show

  1. 不会被标记为条件节点;
  2. 正常生成 VNode,无论条件真假;
  3. 编译器为其添加一个 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、事件监听器、子组件均保留。

✅ 生命周期影响

  • ❌ 不触发 destroyedcreated
  • 组件始终处于"活跃"状态。

📌 适用场景

  • 频繁切换显示状态;
  • 需要保留组件内部状态;
  • 初始渲染性能不敏感。

三、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 指令

  1. 编译器识别 v-html
  2. 生成 AST 时,添加 directives 属性:
js 复制代码
{
  tag: 'div',
  directives: [
    {
      name: 'html',
      value: 'htmlContent',
      rawName: 'v-html'
    }
  ]
}
  1. 移除原有子节点(如文本、其他元素)。

📌 渲染阶段:直接设置 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 安全使用指南

  1. 永远不要 将用户输入直接用于 v-html
  2. 使用 DOMPurify 净化 HTML:
js 复制代码
import DOMPurify from 'dompurify';

const clean = DOMPurify.sanitize(dirtyHTML);
this.htmlContent = clean;
  1. 考虑使用 v-dom(第三方指令)或 dangerouslySetInnerHTML(Vue 3 Composition API)替代。

💡 结语

"理解原理,才能写出高性能、安全的代码。"

  • v-if条件渲染,走的是"销毁-重建"路线;
  • v-show显示控制 ,靠 display: none 隐藏;
  • v-htmlHTML 注入 ,直接设置 innerHTML,快但危险。

掌握它们的底层机制,你就能:

✅ 避免不必要的组件重建;

✅ 提升频繁切换的性能;

✅ 防止 XSS 安全漏洞。

相关推荐
LuckySusu3 小时前
【vue篇】单页 vs 多页:Vue 应用架构的终极对决
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 进阶指南:如何在自定义组件中完美使用 v-model
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue v-model 深度解析:从表单到组件的双向绑定之谜
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 2 响应式系统:Object.defineProperty 的五大缺陷
前端·vue.js
奶糖 肥晨3 小时前
Rokid JSAR 技术开发全指南:基于 Web 技术栈的 AR 开发实战
前端·ar·restful
LuckySusu3 小时前
【vue篇】Vue 中 computed 和 methods 的本质区别:缓存的艺术
前端·vue.js
Python私教3 小时前
React + Ant Design + Tailwind CSS 打造「无痕」垂直滚动区域:功能全上,滚动条隐身
前端·css·react.js
LuckySusu3 小时前
【vue篇】Vue 中 computed 和 watch 的终极对比:何时用谁?
前端·vue.js
LuckySusu3 小时前
【vue篇】Vue 过滤器(Filters)完全指南:优雅处理数据展示
前端·vue.js