为啥 v-if 和 v-for 不建议放一起使用?

1,153 阅读4分钟

v-ifv-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 中的执行结果:

86.png

Vue3 中的执行结果:

87.png

具体是因为在 Vue2 中,当 v-ifv-for 在同一元素上时,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中,所以 v-if 可以访问到 v-for 定义的局部变量(在这里是 todo),因此页面可以正常渲染。

而在 Vue3 中,当 v-ifv-for 在同一元素上时,v-ifv-for 的优先级更高。这意味着 v-if 的条件表达式将无法访问到 v-for 定义的局部变量(在这里是 todo),因此页面无法正常渲染。

当然,在 Vue3 中,这可以通过在外层加一个 template 标签,将 v-ifv-for 分离来解决:

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>      
</template>

v-ifv-for 在同一节点的时候,Vue2 和 Vue3 的处理方式不同,这也是 Vue2 和 Vue3 的差异之一。

要注意的是,无论 Vue2 ,还是 Vue3 都不建议将 v-ifv-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 提供的模板编译工具,可得到如下渲染函数:

129.png

👆 不要被 Vue version: 3.4.38 迷惑,上面的编译结果就是 Vue2 的编译器编译后的结果。

在这里简单解释一下:

  • _c 实际上就是 createElement 函数,作用是创建虚拟 DOM 。

  • _v 实际上就是 createTextVNode 函数,作用是创建文本类型的虚拟 DOM。

  • _e 实际上就是 createEmptyVNode 函数,作用是创建一个空的注释节点。

  • _l 函数实际上是 Vue 内部渲染列表的函数,即 renderList 函数,是 v-for 指令最终的编译结果。

渲染函数中的 !todo.isCompletev-if 中的条件表达式。

从最终的编译出来的渲染函数中可知,对 Vue2 来说,会先走 v-for 的逻辑,再根据 v-if 的条件判断是否渲染这个元素,如果没命中 v-if 的条件,则渲染一个空注释节点。

130.png

可以发现,v-for 遍历多少次,就要执行判断多少次,对性能来说是不够友好的。

因此 Vue2 的官方文档也推荐我们,先使用计算属性过滤出需要渲染的列表,在模板中直接使用 v-for 渲染过滤后的列表,就不需要在循环中重复执行判断逻辑,性能更好。同时也解耦渲染层的逻辑,可维护性 (对逻辑的更改和扩展) 更强。

131.png

Vue3

对于下面的模板:

<div>
  <li v-for="todo in todos" :key="todo.name" v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</div>

借助 Vue3 提供的模板编译工具,可得到如下渲染函数:

132.png

从编译结果可知,在 Vue3 中,v-forv-if 一起使用,会先走 v-if 再走 v-for ,即 v-if 的优先级比 v-for 高,

133.png

在 Vue3 中,相当于把 v-if 提升了,这样可以避免在循环中多次执行判断的情况,但是这也导致了 v-if 的条件判断表达式无法访问到 v-for 中定义的局部变量,导致运行时报错。

因此,在 Vue3 中,v-forv-if 也不建议放到一起使用。

总结

在 Vue2 中,v-forv-if 在一起使用的话,v-for 的优先级高于 v-if ,这会导致 v-for 遍历多少次就会执行多少次 v-if 的判断,这对性能来说不够友好,所以不建议 v-forv-if 放在一起使用。

在 Vue3 中,v-forv-if 在一起使用的话,v-if 的优先级高于 v-forv-if 的条件判断表达式会提升到 v-for 前执行,如果 v-if 中的条件表达式依赖了 v-for 中定义的局部变量,会导致 v-if 中的条件表达式无法访问到 v-for 中定义的局部变量,从而导致运行时报错,因此也不建议 v-forv-if 放在一起使用。

当遇到了列表渲染和条件渲染同时存在的场景,最佳实践是:

  • 使用计算属性,先过滤出需要渲染的列表,再使用 v-for 在模板中渲染真正需要渲染的列表,从而避免在循环中多次执行条件判断。

  • 使用 <template> 或者其他标签,将 v-forv-if 分离,避免 v-forv-if 放一起使用。

计算属性:

131.png

使用 <template>v-forv-if 分离:

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>