v-for比v-if优先级更高?
Category(分类): Vue Status: 已校订(Vue 2 / Vue 3 对照)
自我介绍
大家好,我叫 JetTsang,之前都是在掘金潜水,现在偶尔做一些内容输出。
前言
v-if 和 v-for 哪个优先级更高?这个问题必须先限定 Vue 版本,而且这里的“优先级”指的是同一元素上结构指令的编译顺序与作用域规则,不是 JavaScript 运算符优先级。
<!-- 不推荐:同一个元素同时使用 v-for 和 v-if -->
<div
v-for="item in itemList"
v-if="item.id === 1"
:key="item.id"
>
{{ item.name }}
</div>
核心结论:
- Vue 2:同元素上
v-for优先于v-if,所以v-if能访问循环别名item,但会先遍历再逐项判断。 - Vue 3:同元素上
v-if优先于v-for,所以v-if条件中访问不到循环别名item。 - Vue 2 和 Vue 3 都不推荐把两者写在同一元素上。 应根据意图改写模板。
为什么要看编译结果
v-if 是条件渲染,v-for 是列表渲染,二者都是 Vue 模板语法,需要经过编译,再由 render function 生成 VNode 并 patch 到 DOM。

可以使用对应版本的 Template Explorer 辅助观察:
具体 render 输出会随编译器版本和选项变化,不能把某张截图的代码形态当作稳定公共 API;稳定结论应以官方文档和对应版本源码为准。
示例数据:
const list = [
{ name: 'JetTsang', id: 1, active: true },
{ name: 'juejin', id: 2, active: false },
{ name: 'Vue', id: 3, active: true },
]
Vue 2:v-for 先处理
Vue 2 编译同节点指令时,先生成列表迭代,再在每次迭代里判断条件。原作者把模板放进 Vue 2 Template Explorer,得到如下 render 结果:


这段历史输出里的 helper 可这样读:
_c()是编译器生成代码使用的createElementhelper,用来创建 VNode;它是 Vue 2 内部 render helper 形态,不是建议业务代码依赖的稳定 API。_l(list, callback)是renderListhelper:遍历list,把当前item传给回调。- 条件表达式位于
_l的循环回调中,因此先建立item作用域,再逐项计算item.id === 1。 - 未命中条件时,生成
_e()对应的空/注释 VNode;原作者也展示了注释节点的实际形态:

因此在 Vue 2 中,这段代码的 item 有效,但每个列表项都要执行条件判断。如果目的是过滤列表,官方建议先用计算属性得到过滤结果;原作者截图中的开发提示也是这一上下文:

需要强调:即使 Vue 2 允许别名这样使用,同节点合用仍是官方风格指南中应避免的写法。
Vue 3:v-if 先处理
Vue 3 中,同节点 v-if 的优先级高于 v-for。这意味着编译 v-if="item.id === 1" 时,v-for 尚未建立 item 的循环作用域;item 会被当成组件实例上下文属性,通常得到“属性未定义”警告或运行时错误。


原文把变化描述为“把 v-if 提升到循环外以节省计算”,容易造成误解:它并不是让原来的同节点写法在 Vue 3 中合法并自动获得性能优化。原写法访问不到 item,应直接重写。这个变化也不能武断归因于单一性能原因,它首先是 Vue 3 明确的编译顺序和作用域语义。
以下两图明确是原作者当时的比较和推导过程:作者尝试把 v-if 放到外层 <template>,再把 v-for 包在其中,比较生成的 render 形态,并据此推导 Vue 3 是“单纯性能优化”。截图保留其历史上下文,但这个归因不成立:改写后的条件若依赖 item 仍没有循环作用域,而且不同模板语义不能只凭输出外形相近视为等价。


两种必须掌握的正确写法
写法一:计算属性预过滤
当需求是“只展示符合条件的项”时,先用 computed 过滤。过滤逻辑可复用、可测试,模板也更清楚。
<script setup lang="ts">
import { computed, ref } from 'vue'
interface User {
id: number
name: string
active: boolean
}
const users = ref<User[]>([
{ id: 1, name: 'JetTsang', active: true },
{ id: 2, name: 'juejin', active: false },
{ id: 3, name: 'Vue', active: true },
])
const activeUsers = computed(() =>
users.value.filter((user) => user.active),
)
</script>
<template>
<ul>
<li v-for="user in activeUsers" :key="user.id">
{{ user.name }}
</li>
</ul>
</template>
Vue 2 Options API 对应写法:
computed: {
activeUsers() {
return this.users.filter((user) => user.active)
},
}
写法二:外层 <template v-for>,内层元素 v-if
如果条件确实必须逐项判断,并且需要访问循环别名,先由 <template v-for> 建立作用域,再在内部元素上判断:
<template v-for="user in users" :key="user.id">
<li v-if="user.active">
{{ user.name }}
</li>
</template>
Vue 3 中 key 应放在 <template v-for> 上,而不是放到内部 li。这正是任务要求的逐项条件写法。
如果维护 Vue 2,<template> 不能充当带 key 的真实元素,通常要把 key 放到实际子元素:
<!-- Vue 2 历史写法 -->
<template v-for="user in users">
<li v-if="user.active" :key="user.id">
{{ user.name }}
</li>
</template>
补充:控制整个列表是否显示
若条件与单个 item 无关,例如“用户有权限时才显示整组列表”,将 v-if 放在容器上,避免每项重复判断:
<ul v-if="canViewUsers">
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</ul>
这与“过滤 item”是不同意图,不必一律使用 computed。
选择对照
| 需求 | 推荐写法 |
|---|---|
| 只展示符合条件的项 | computed 预过滤后 v-for |
| 整个列表显示/隐藏 | 容器 v-if,子元素 v-for |
| 必须逐项判断且访问别名 | Vue 3:<template v-for :key> + 内层元素 v-if |
同元素同时写 v-for 和 v-if | Vue 2/3 都应避免 |
总结
Vue 2 同元素上 v-for 优先,Vue 3 同元素上 v-if 优先;Vue 3 的条件因而访问不到 v-for 别名。不要把这个变化简单解释成一次“性能提升”,也不要继续依赖同节点合用。按项过滤使用 computed;条件必须逐项判断时,在 Vue 3 使用外层 <template v-for :key>、内层元素 v-if。
加餐:编译过程
从概念上看,Vue 模板编译会经历:
- parse:把模板字符串解析为 AST;
- transform / optimize:转换结构指令并分析可优化信息;
- generate:生成 render function 代码。
不同 Vue 大版本的编译器内部阶段和输出形式并不完全相同,学习源码时应固定版本。
官方参考
- Vue 3:列表渲染中的
v-for与v-if - Vue 3 风格指南:避免同节点使用
- Vue 3:
template v-for的 key - Vue 2:
v-for与v-if - Vue 2 风格指南:避免同节点使用
- Vue 2.7 compiler codegen
- Vue 3 compiler transform preset
作者:JetTsang 原文链接:https://juejin.cn/post/7217810344696594488 来源:稀土掘金。著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。