在vue2和vue3的官方文档里都写到不推荐 v-if 和 v-for 同时使用,如下代码所示:

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

一、vue3文档:列表渲染 | Vue.js

在vue3中,是因为当它们同时存在于一个节点上时,v-if 比 v-for 的优先级更高。这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名。 v-if里是无法访问到todo的,这将会报错。

二、vue2文档:列表渲染 — Vue.js

在vue2中,v-for 的优先级比 v-if 更高,也就是说在v-if中可以访问到v-for作用域内定义的变量别名 ,因此不会跟vue3一样报错,但并不推荐这么做,原因如下:

  1. 性能问题:将 v-for 和 v-if 放在同一个元素上会导致性能下降。Vue 必须为每一个在 v-for 中的项目都检查 v-if 的条件,这会增加不必要的计算量。特别是当 todos 数组很大时,这种性能问题会更加明显。详见文章末尾的附录。
  2. 逻辑可读性:从逻辑和可读性的角度来看,将过滤逻辑(v-if)和渲染逻辑(v-for)混合在一起可能会导致代码难以理解和维护。最好是先过滤数据,然后再进行渲染。

推荐写法:

写法一、先过滤数据,再使用v-for

<template>
  <ul>
    <li v-for="todo in filteredTodos" :key="todo.id">
      {{ todo.text }}
    </li>
  </ul>
</template>
 
<script>
export default {
  data() {
    return {
      todos: [
        { id: 1, text: 'Learn Vue', isComplete: false },
        { id: 2, text: 'Build something awesome', isComplete: true },
        // ... more todos
      ]
    }
  },
  computed: {
    // 得到过滤后的数据
    filteredTodos() {
      return this.todos.filter(todo => !todo.isComplete);
    }
  }
}
</script>

写法 二:利用<template>元素,将 v-if 放在 v-for 的子元素中,而不是与 v-for 直接放在同一个元素上。(在vue2中,不推荐使用,逻辑可读性虽然没问题,但数据多时,还是可能存在性能问题;在 Vue 3 中,编译器能够识别 v-for 在 v-if 的子元素上的使用情况,并进行优化,以避免不必要的虚拟 DOM 节点的创建,可以使用该写法)

<template>
  <ul>
    <template v-for="todo in todos" :key="todo.id">
      <li v-if="!todo.isComplete">
        {{ todo.text}}
      </li>
    </template>
  </ul>
</template>
 
<script>
export default {
  data() {
    return {
      todos: [
        { id: 1, text: 'Learn Vue', isComplete: false },
        { id: 2, text: 'Build something awesome', isComplete: true },
        // ... more todos
      ]
    }
  }
}
</script>

附录:

在 Vue 2 中,当你同时在一个元素上使用 v-for 和 v-if,Vue 的渲染逻辑实际上是这样的:

  1. v-for 优先于 v-if:首先,v-for 会为 todos 数组中的每个项目创建 DOM 节点。这意味着,它会为数组中的每个 todo 项目都创建一个 <li> 元素。
  2. v-if 随后应用:然后,Vue 会检查每个由 v-for 创建的 <li> 元素上的 v-if 条件。如果 v-if 的条件不满足(即 todo.isComplete 为 true),则该节点不会被渲染到 DOM 中。但是,这并不意味着节点被销毁,而是它们只是简单地不被添加到 DOM 树中。
  3. 节点的销毁与复用:如果 todos 数组发生变化(例如,项目被添加、删除或更改),Vue 会重新计算 v-for 和 v-if 的结果,并相应地更新 DOM。这意味着,即使 v-if 条件不满足,由 v-for 创建的节点可能仍然存在在 Vue 的虚拟 DOM 中,只是它们不会出现在实际的 DOM 树中。这些节点可以被 Vue 复用,如果它们在未来再次满足 v-if 的条件。
  4. 性能影响:即使某些节点因为 v-if 条件不满足而不会被渲染到 DOM 中,它们仍然会被 Vue 创建和追踪,这可能会对性能产生影响,尤其是当 todos 数组很大时。
  5. 最佳实践:为了获得最佳性能,你应该避免在同一元素上同时使用 v-for 和 v-if。如果可能,应该使用计算属性或方法来预先过滤数据,然后只对过滤后的结果进行 v-for 渲染。这样做可以减少不必要的节点创建和销毁,提高应用程序的性能。

因此,对于你给出的代码示例,即使 todo.isComplete 为 true,由 v-for 创建的 <li> 节点仍然会被 Vue 创建和追踪,只是它们不会出现在最终的渲染结果中。为了优化性能,你应该将过滤逻辑移至计算属性或方法中,并在 v-for 中仅渲染过滤后的数据。