Appearance
✨ 指令的本质 👌
要点速览
- 指令在编译后变成普通 JS 逻辑:条件分支、列表渲染、属性绑定、事件绑定。
- 依赖建立发生在渲染函数内对响应式数据的“读取拦截”,数据变化驱动组件级更新。
- 指令是语法糖:
v-if/v-for/v-bind/v-on分别对应不同的渲染代码生成路径。
目前为止,我们学习过很多 Vue 的内置指令,例如:v-if、v-show、v-for、v-model、v-html、v-bind、v-on 等。下面结合 vite-plugin-inspect 插件的编译结果来分析指令的本质。
v-if:条件分支的代码生成
html
<template>
<div v-if="type === 1">type 的值为 1</div>
<div v-else-if="type === 2">type 的值为 2</div>
<div v-else-if="type === 3">type 的值为 3</div>
<div v-else-if="type === 4">type 的值为 4</div>
<div v-else>Not 1/2/3/4 is 0</div>
<button @click="toogleFunc">Toggle</button>
</template>
<script setup>
import { ref } from "vue";
const type = ref(1);
function toogleFunc() {
type.value = Math.floor(Math.random() * 5);
}
</script>编译结果与说明

v-if在编译后对应的是“条件分支/三目”等普通 JS 分支结构。- 每次
$setup.type值变化,渲染函数重新执行,命中不同分支以生成不同的虚拟 DOM。
v-for:列表渲染的代码生成
html
<template>
<div>
<h2>商品列表</h2>
<ul>
<!-- 使用 v-for 遍历 products 数组,渲染每个商品的信息 -->
<li v-for="(product, index) in products" :key="index">
{{ product.name }} - ${{ product.price }}
</li>
</ul>
</div>
</template>
<script setup>
import { ref } from "vue";
const products = ref([
{ name: "键盘", price: 99.99 },
{ name: "鼠标", price: 59.99 },
{ name: "显示器", price: 299.99 },
]);
</script>编译结果与说明

- 生成的渲染函数使用了内部方法
renderList来遍历并生成子节点。 - 源码位置:
packages/runtime-core/src/helpers/renderList.ts
v-for 常见注意点
:key应稳定唯一,避免使用index在可变列表中造成复用错误。- 列表每项如果读取响应式数据成员会建立依赖;变更时仅受影响的部分重新渲染。
v-bind:属性绑定的代码生成
html
<template>
<div v-bind:id>dynamicId</div>
</template>
<script setup>
import { ref } from "vue";
const id = ref("my-id");
</script>编译结果与说明

- 将
$setup.id的值作为div的id属性值,读取行为触发“读取拦截”,与渲染函数建立依赖。 $setup.id改变时渲染函数重新执行,对应的属性随之更新。
v-on:事件绑定的代码生成
html
<template>
<div>{{ count }}</div>
<button v-on:click="count++">+1</button>
</template>
<script setup>
import { ref } from "vue";
const count = ref(0);
</script>编译结果与说明

- 编译结果是为
button添加click事件,处理函数为:
jsx
($event) => $setup.count++;- 事件回调中对响应式数据的写入会触发变更与重新渲染调度。
结论与小结
总结
- 编译出来的渲染函数本身“没有指令”,不同指令被编译为不同的普通 JS 处理逻辑。
- 依赖建立源于渲染函数对响应式数据的“读取拦截”;数据变化驱动组件级更新。
- 指令更多是模板层的“语法糖”,理解其编译后的形态更利于定位和优化渲染行为。
