在 Vue 的模板中,v-if 和 v-for 同时写在一个元素上,是新手很容易踩的坑。它看起来像是“遍历列表,并只渲染符合条件的项”,但实际执行逻辑和你想的可能不一样。
问题表现
<!-- ❌ 错误写法 -->
<ul>
<li v-for="item in list" v-if="item.active" :key="item.id">
{{ item.name }}
</li>
</ul>
这段代码的意图是:遍历 list 数组,只把 active 为 true 的项渲染出来。直觉上会认为先判断 v-if,再执行 v-for,但 Vue 实际的处理顺序相反。
原因:优先级导致的性能浪费
在 Vue 2 和 Vue 3 中,v-for 的优先级高于 v-if。这意味着模板在编译时,会先完整遍历 list 数组,为每一项生成 VNode,然后才对每个 VNode 应用 v-if 条件判断。如果 list 有 100 条数据,其中只有 5 条满足条件,Vue 仍然会先循环 100 次,再丢掉 95 次——徒增计算开销。
更严重的是,当 v-if 依赖的条件是组件内部的某个响应式数据时,这种写法还可能导致难以追踪的渲染错误,因为作用域已经混乱。
正确做法:用计算属性提前过滤
解决这个问题的标准方案是:将 v-for 和 v-if 分离,在 JavaScript 层面完成数据过滤,模板里只做纯粹的遍历。
<template>
<ul>
<!-- ✅ 只遍历处理后的数据 -->
<li v-for="item in activeList" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
<script setup>
import { ref, computed } from 'vue'
const list = ref([
{ id: 1, name: '任务A', active: true },
{ id: 2, name: '任务B', active: false },
{ id: 3, name: '任务C', active: true }
])
const activeList = computed(() => list.value.filter(item => item.active))
</script>
这样做有三个好处:
- 性能更优:只遍历真正需要渲染的数据,避免无效循环。
- 逻辑清晰:数据处理逻辑集中在
computed里,模板保持简洁。 - 可复用:
activeList可以在模板多处使用,也可以被其他计算属性依赖。
特殊情况:如果确实需要按条件过滤并使用列表
如果过滤条件依赖的不是列表项本身的属性,而是一个外部变量(如 showAll),可以在 v-if 提升到外层容器,而不是直接用在循环元素上:
<!-- ✅ 把 v-if 提升到外层容器 -->
<template v-if="showAll">
<li v-for="item in list" :key="item.id">
{{ item.name }}
</li>
</template>
这样 Vue 只会执行一次条件判断,不会再对列表的每一项做无意义检核。
一句话总结:永远不要在同一个元素上同时使用 v-if 和 v-for。先处理好数据,再用 v-for 纯遍历,这是 Vue 官方推荐的黄金法则。