Skip to content

✨ 指令的本质 👌

要点速览

  • 指令在编译后变成普通 JS 逻辑:条件分支、列表渲染、属性绑定、事件绑定。
  • 依赖建立发生在渲染函数内对响应式数据的“读取拦截”,数据变化驱动组件级更新。
  • 指令是语法糖:v-if/v-for/v-bind/v-on 分别对应不同的渲染代码生成路径。

目前为止,我们学习过很多 Vue 的内置指令,例如:v-ifv-showv-forv-modelv-htmlv-bindv-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 的值作为 divid 属性值,读取行为触发“读取拦截”,与渲染函数建立依赖。
  • $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 处理逻辑。
  • 依赖建立源于渲染函数对响应式数据的“读取拦截”;数据变化驱动组件级更新。
  • 指令更多是模板层的“语法糖”,理解其编译后的形态更利于定位和优化渲染行为。