v-if 和 v-for 都是 Vue 提供的指令。
-
v-if用于模板的条件渲染,当v-if传入的值为 true 时,则渲染该内容 -
v-for用于模板的列表渲染,会循环地渲染模板内容
const todos = ref([
{ isComplete: false, name: '旅游' },
{ isComplete: false, name: '运动' },
{ isComplete: true, name: '搞卫生' },
])
<li v-for="todo in todos" :key="todo.name" v-if="!todo.isComplete">
{{ todo.name }}
</li>
上面的代码,在 Vue2 中可以正常执行,但是在 Vue3 中却会报错
Vue2 中的执行结果:
Vue3 中的执行结果:
具体是因为在 Vue2 中,当 v-if 和 v-for 在同一元素上时,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中,所以 v-if 可以访问到 v-for 定义的局部变量(在这里是 todo),因此页面可以正常渲染。
而在 Vue3 中,当 v-if 和 v-for 在同一元素上时,v-if 比 v-for 的优先级更高。这意味着 v-if 的条件表达式将无法访问到 v-for 定义的局部变量(在这里是 todo),因此页面无法正常渲染。
当然,在 Vue3 中,这可以通过在外层加一个 template 标签,将 v-if 和 v-for 分离来解决:
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
v-if 和 v-for 在同一节点的时候,Vue2 和 Vue3 的处理方式不同,这也是 Vue2 和 Vue3 的差异之一。
要注意的是,无论 Vue2 ,还是 Vue3 都不建议将 v-if 和 v-for 放到同一个元素上使用。而在 Vue3 中,由于 v-if 的优先级比 v-for 高,导致 v-if 中的条件表达式无法访问 v-for 中定义的局部变量,从而导致报错。
从模板编译的结果看待这个问题
Vue2
对于下面的模板:
<div>
<li v-for="todo in todos" :key="todo.name" v-if="!todo.isComplete">
{{ todo.name }}
</li>
</div>
借助 Vue2 提供的模板编译工具,可得到如下渲染函数:
👆 不要被
Vue version: 3.4.38迷惑,上面的编译结果就是 Vue2 的编译器编译后的结果。
在这里简单解释一下:
-
_c实际上就是createElement函数,作用是创建虚拟 DOM 。 -
_v实际上就是createTextVNode函数,作用是创建文本类型的虚拟 DOM。 -
_e实际上就是createEmptyVNode函数,作用是创建一个空的注释节点。 -
_l函数实际上是 Vue 内部渲染列表的函数,即renderList函数,是v-for指令最终的编译结果。
渲染函数中的 !todo.isComplete 是 v-if 中的条件表达式。
从最终的编译出来的渲染函数中可知,对 Vue2 来说,会先走 v-for 的逻辑,再根据 v-if 的条件判断是否渲染这个元素,如果没命中 v-if 的条件,则渲染一个空注释节点。
可以发现,v-for 遍历多少次,就要执行判断多少次,对性能来说是不够友好的。
因此 Vue2 的官方文档也推荐我们,先使用计算属性过滤出需要渲染的列表,在模板中直接使用 v-for 渲染过滤后的列表,就不需要在循环中重复执行判断逻辑,性能更好。同时也解耦渲染层的逻辑,可维护性 (对逻辑的更改和扩展) 更强。
Vue3
对于下面的模板:
<div>
<li v-for="todo in todos" :key="todo.name" v-if="!todo.isComplete">
{{ todo.name }}
</li>
</div>
借助 Vue3 提供的模板编译工具,可得到如下渲染函数:
从编译结果可知,在 Vue3 中,v-for 和 v-if 一起使用,会先走 v-if 再走 v-for ,即 v-if 的优先级比 v-for 高,
在 Vue3 中,相当于把 v-if 提升了,这样可以避免在循环中多次执行判断的情况,但是这也导致了 v-if 的条件判断表达式无法访问到 v-for 中定义的局部变量,导致运行时报错。
因此,在 Vue3 中,v-for 和 v-if 也不建议放到一起使用。
总结
在 Vue2 中,v-for 和 v-if 在一起使用的话,v-for 的优先级高于 v-if ,这会导致 v-for 遍历多少次就会执行多少次 v-if 的判断,这对性能来说不够友好,所以不建议 v-for 和 v-if 放在一起使用。
在 Vue3 中,v-for 和 v-if 在一起使用的话,v-if 的优先级高于 v-for ,v-if 的条件判断表达式会提升到 v-for 前执行,如果 v-if 中的条件表达式依赖了 v-for 中定义的局部变量,会导致 v-if 中的条件表达式无法访问到 v-for 中定义的局部变量,从而导致运行时报错,因此也不建议 v-for 和 v-if 放在一起使用。
当遇到了列表渲染和条件渲染同时存在的场景,最佳实践是:
-
使用计算属性,先过滤出需要渲染的列表,再使用
v-for在模板中渲染真正需要渲染的列表,从而避免在循环中多次执行条件判断。 -
使用
<template>或者其他标签,将v-for和v-if分离,避免v-for和v-if放一起使用。
计算属性:
使用 <template> 将 v-for 和 v-if 分离:
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>