从底层看为什么 Vue 中不建议同时使用 v-if 和 v-for?

326 阅读3分钟

关键点

在 Vue 3 中,v-if 的优先级 实际上要高于 v-for,所以 v-if 和 v-for 一起使用是没问题的。但是官方还是不推荐把 v-if 和 v-for 一起使用

在 Vue 2 中,v-for 比 v-if 具有更高的优先级,即使要渲染的列表中,只有一小部分元素是需要被渲染出来的,Vue 也得在每次重渲染的时候遍历整个列表,然后才能判断哪些元素是需要被渲染的,数据量比较大的情况下,开销会较大。

Vue 2 中的 v-if 和 v-for 一起使用的具体表现

我们使用 vue-template-compiler 这个包来看解析一下模板,以下面代码为例:

const compiler = require('vue-template-compiler')

const res = compiler.compile(`<div>
  <div v-for="(item, index) in renderedList" v-if="item.show">被渲染的元素</div>
</div>`)

我们只需要看 render 这个属性的值就好了,因为它决定了如何创建 VNodes。

with(this) {
  return _c('div',
    _l(renderedList, (item, index) => {
      return item.show ? _c('div', [_v("被渲染的元素")]) : _e();
    })
  );
}

相关方法:

_c: 创建 VNode 节点
_l: 处理 v-for 指令,第一个接收的参数为渲染数据,第二接收的参数是数据处理回调
_v: 创建文本节点
_e: 创建一个空的 VNode 节点

我们可以看到,当 v-for 与 v-if 一起使用的时候,_l 是先执行的,而 v-if 的处理条件是在 _l 的数据处理回调里面的。

所以我们可以得出,在 Vue 2 中,v-for 的优先级是要比 v-if 优先级要高的

Vue 3 中的 v-if 和 v-for 一起使用的具体表现

我们使用 @vue/compiler-sfc 这个包来看解析一下模板,以下面代码为例:

const {
  compileTemplate,
} = require('@vue/compiler-sfc')

function compile(opts) {
  return compileTemplate({
    ...opts,
    id: '',
  })
}

const source = `<div>
  <div v-for="(item, index) in renderedList" v-if="item.show">需要被渲染的元素</div> 
</div>`

const result = compile({ filename: 'example.vue', source })

我们只需要看 code 这个属性的值就好了,因为它决定了如何创建 VNodes。

import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode } from "vue"

export function render(_ctx, _cache) {
  return (_openBlock(), _createElementBlock("div", null, [
    // 关键点,v-if 处理在前,v-for 处理在后
    (_ctx.item.show)
      ? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(_ctx.renderedList, (item, index) => {
          return (_openBlock(), _createElementBlock("div", null, "需要被渲染的元素"))
        }), 256 /* UNKEYED_FRAGMENT */))
      : _createCommentVNode("v-if", true)
  ]))
}

关键方法:

_createElementBlock: 创建 VNode 节点
_renderList: 处理 v-for 指令,第一个接收的参数为渲染数据,第二接收的参数是数据处理回调
_createCommentVNode: 创建注释节点

我们可以看到,当 v-for 与 v-if 一起使用的时候,v-if 的判断是先执行的,而 v-for 的处理是在 v-if 条件满足后再进行处理的。

为什么 Vue 3 还是不推荐把 v-for 和 v-if 一起使用?

来看一个案例:

<ul>
  <li
    v-for="user in users"
    v-if="user.isActive"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>

放到浏览器上去执行的时候,就会发现报错了:

111.png

原因:v-if 的优先级要比 v-for 要高,所以,这里 v-if 条件实际上是拿不到 v-for 中遍历出来的变量 user 的

一般处理方法

对于这个案例:

<ul>
  <li
    v-for="user in users"
    v-if="user.isActive"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>

有 2 个解决方法:

1、将遍历数据用 computed 先过滤一遍:

<template>
  <ul>
    <li
      v-for="user in activedUser"
      :key="user.id"
    >
      {{ user.name }}
    </li>
  </ul>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue'

const users = ref([
  {
    isActive: true,
    id: 1,
    name: 'zs'
  }
])

const activedUser = computed(() => {
  return users.value.filter(user => user.isActive)
})
</script>

2、将 v-if 放到 v-for 的元素下,并且防止有多余的元素,使用 template 进行包裹

<ul>
  <template v-for="user in users" :key="user.id">
    <li v-if="user.isActive">
      {{ user.name }}
    </li>
  </template>
</ul>