3.2 指令系统全景


文档摘要

3.2 指令系统全景 本节摘要:指令是模板里带 v- 前缀的特殊属性,按作用可分为条件渲染、列表渲染、双向绑定、样式与显隐四类。本节逐类拆解它们在编译产物与运行成本上的差异——尤其是 v-if 与 v-show 这对高频面试题的量化对比,以及自定义指令的钩子模型。 你应当能获得的能力 阅读完本节,你应当能够: 按成本模型给四类指令分类,而不是死记清单; 从"编译产物 + 切换频率"两个维度决策 v-if / v-show; 写出带全部钩子的自定义指令,并说明各钩子的触发时机; 解释 v-for 与 v-if 不建议同级的编译原因。 指令的成本模型 指令不是平等的。按"贵不贵"分三档: 贵(动真实 DOM 结构):v-if 系——创建与销毁节点,连带组件的话还要走完整生命周期;

3.2 指令系统全景

本节摘要:指令是模板里带 v- 前缀的特殊属性,按作用可分为条件渲染、列表渲染、双向绑定、样式与显隐四类。本节逐类拆解它们在编译产物与运行成本上的差异——尤其是 v-if 与 v-show 这对高频面试题的量化对比,以及自定义指令的钩子模型。

你应当能获得的能力

阅读完本节,你应当能够:

  1. 按成本模型给四类指令分类,而不是死记清单;
  2. 从"编译产物 + 切换频率"两个维度决策 v-if / v-show;
  3. 写出带全部钩子的自定义指令,并说明各钩子的触发时机;
  4. 解释 v-for 与 v-if 不建议同级的编译原因。

指令的成本模型

指令不是平等的。按"贵不贵"分三档:

  • 贵(动真实 DOM 结构):v-if 系——创建与销毁节点,连带组件的话还要走完整生命周期;
  • 中(动属性与内容):v-bind、v-html、v-text——改已有节点的属性或 innerHTML;
  • 便宜(动样式或监听):v-show——只切 display 样式。

带着这个分级看具体指令。

条件渲染:v-if 家族对 v-show

<!-- v-if:不满足时产物里根本没有这个节点 --> <div v-if="isVip">尊享内容</div> <div v-else-if="isMember">会员内容</div> <div v-else>普通内容</div> <!-- v-show:节点始终存在,只是被 display:none 藏住 --> <div v-show="visible">悬浮提示</div>

编译产物差异直观:

// v-if 的渲染函数(简化) isVip ? createElement('div', ..., '尊享内容') : createEmptyVNode(); // v-show 的渲染函数(简化) createElement('div', { directives: [{ name: 'show', value: visible }] }, '悬浮提示');

v-if 分支不命中时返回空节点,切换时销毁/重建(组件场景连带卸载挂载);v-show 产物恒在,运行时改样式。

维度 v-if v-show
初始不命中时 零渲染成本 照常渲染,仅隐藏
切换成本 高(销毁重建) 低(改样式)
支持template与v-else
适用 条件很少变、分支重 高频切换、轻量内容

决策一句话:切换频率低用 v-if,频率高用 v-show;分支是重组件(表单、图表)几乎无脑 v-if——白渲染一个藏起来的重组件最亏。

顺带一条编译级忠告:v-for 与 v-if 别写在同一元素上。Vue 2 里 v-for 优先级更高,v-if 在每次循环里逐个判断;Vue 3 反过来 v-if 优先,拿不到循环变量直接报错。要过滤列表,用计算属性先滤:

computed: { activeItems() { return this.items.filter(i => i.active); } }

列表渲染:v-for 的产物

<li v-for="(item, index) in items" :key="item.id"> {{ index }} - {{ item.name }} </li>

编译产物是一个循环生成的 vnode 数组,每项带着 key。key 不是给人看的注释,是 diff 阶段识别"同一节点"的身份证——第 4 章专门剖析,这里先立规矩:用稳定业务 id,别用数组索引。索引在插入删除时会错位,导致复用错节点、输入类内容串位的经典 bug。

双向绑定:v-model 的糖衣

表单上 v-model 展开是两件事的组合:

<input v-model="text"> <!-- 等价于 --> <input :value="text" @input="text = $event.target.value">

语法糖在受控组件上还有微调:复选框绑定 checked 与 change、单选与下拉同理;组件上 v-model 展开为 prop 与 event 的约定(Vue 2 是 value/input,Vue 3 改为 modelValue/update:modelValue,并支持 v-model:title 这类多参数形式)。第 8 章表单专题会展开,此处记住"双向绑定 = 单向绑定 + 反向事件"这一本质,它并不是什么双向数据流。

样式与显隐:class 与 style 的绑定

对象与数组两种语法覆盖绝大多数场景:

<div :class="{ active: isActive, 'text-danger': hasError }"></div> <div :class="[baseClass, isActive ? 'active' : '']"></div> <div :style="{ fontSize: size + 'px' }"></div>

产物是普通的属性 vnode,成本低,可放心高频更新。

自定义指令:扩展点在哪

当需求是"对普通底层元素进行底层 DOM 访问"(聚焦、拖拽、埋点曝光),自定义指令比组件更合适。Vue 2 五个钩子,Vue 3 换名并语义对齐组件生命周期:

// Vue 3:一个点击外部区域关闭弹层的指令 const clickOutside = { mounted(el, binding) { el.__handler = (e) => { if (!(el === e.target || el.contains(e.target))) { binding.value(e); } }; document.addEventListener('click', el.__handler); }, unmounted(el) { document.removeEventListener('click', el.__handler); } }; app.directive('click-outside', clickOutside);
<div v-click-outside="close">弹层内容</div>

钩子对照表:

Vue 2 Vue 3 时机
bind mounted 元素插入前 / 插入后
inserted mounted 已插入父节点
update updated 所在组件更新(搬迁前后各一次)
componentUpdated updated 所在组件及子级更新完
unbind unmounted 解绑

一个易踩的坑:指令回调里依赖响应式数据时,更新阶段(updated)需要自己比对新旧值决定是否重执行——指令钩子没有自动依赖追踪,它是编译系统留给你的逃生门,不是响应式系统的一部分。

本节要点回顾

顺带一个可以直接动手的验证:在浏览器开发者工具的性能面板里录一段切换操作,分别对 v-if 与 v-show 版本各录一次,对比脚本耗时与强制重排的条数。你会直观看到 v-if 版本出现节点销毁与创建的调用栈,而 v-show 版本只有样式读写。亲手量过一次,比背十遍对比表印象深刻得多,也顺便熟悉了第 9 章要用的测量工具。

  • 成本分级:动结构最贵、动属性居中、动样式最便宜,选型先看这一层;
  • v-if / v-show:看切换频率与分支重量,重组件高频藏显是双重浪费;
  • v-for 纪律:key 用稳定业务 id;过滤放计算属性,不与 v-if 同级;
  • v-model 本质:单向绑定加反向事件的语法糖,没有魔法双向流;
  • 自定义指令定位:底层 DOM 行为的复用方案,钩子无自动依赖追踪,需手动比对。

指令最终产出的 vnode,正是下一章的主角——虚拟 DOM 与 diff。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U