导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

业务技能

针对性攻坚

AI


Vue3 → v-if 比 v-for 优先级高

过滤列表 - 不推荐一个元素同时使用v-if与v-for

let app = Vue.createApp({
  template: `
    <div>
      <ul>
        <li
          v-for="todo of todoList" :key="todo.id"
          v-if="!todo.completed"
        >
          {{ todo.content }}
        </li>
      </ul>
    </div>
  `,
  data() {
    return {
      todoList: [
        {
          id: 1,
          content: 'CONTENT 1',
          completed: true
        },
        {
          id: 2,
          content: 'CONTENT 2',
          completed: false
        },
        {
          id: 3,
          content: 'CONTENT 3',
          completed: true
        }
      ]
    }
  }
}).mount('#app');

Untitled

报错解释

原因: v-if 优先级比 v-for 高

我们其实可以通过查看 render 函数的处理方式来佐证优先级:

let app = Vue.createApp({
  template: `
    <ul>
      <li
        v-for="todo of todoList" :key="todo.id"
        v-if="todoList.length !== 0"
        >
        {{ todo.content }}</li>
    </ul>
  `,
  data() {
    return {
      todoList: [...]
    }
  }
}).mount('#app');

console.log(app.$options.render);

Untitled

我们单击这条打印(单击函数名 render):

Untitled

我们会发现解析之后是:

  1. 先执行的 v-if 的命令 todoList.length !== 0
  2. 而后才循环 todoList 渲染 li 元素的

解决方案

⚪️ 利用 template

<ul>
  <template v-for="todo of todoList" :key="todo.id">
    <li
        v-if="!todo.completed"
        >{{ todo.content }}</li>
  </template>
</ul>

⚪️ 利用 computed

let app = Vue.createApp({
  template: `
    <ul>
      <li v-for="todo of NotCompletedTodoList" :key="todo.id">{{ todo.content }}</li>
    </ul>
  `,
  computed: {
    NotCompletedTodoList() {
      return this.todoList.filter(todo => !todo.completed);
    }
  },
  data() {
    return {
      todoList: [
        {
          id: 1,
          content: 'CONTENT 1',
          completed: true
        },
        {
          id: 2,
          content: 'CONTENT 2',
          completed: false
        },
        {
          id: 3,
          content: 'CONTENT 3',
          completed: true
        }
      ]
    }
  }
}).mount('#app');

列表是否有值时 - 可以一个元素同时使用

let app = Vue.createApp({
  template: `
    <ul>
      <li
        v-for="todo of todoList" :key="todo.id"
        v-if="todoList.length > 0"
        >{{ todo.content }}</li>
    </ul>
  `,
  data() {
    return {
      todoList: []
    }
  }
}).mount('#app');

解释

上述情况虽然 v-if 与 v-for 一起使用了,但 v-if 中的 todoList 是存在在data属性中的,所以不会报错也能联合使用。 但并不推荐这样!!!(要养成良好习惯不要一起使用,虽然不报错)

推荐写法

把 v-if 提到 ul 父元素上

let app = Vue.createApp({
  template: `
    <ul v-if="todoList.length > 0">
      <li
        v-for="todo of todoList" :key="todo.id">
        {{ todo.content }}</li>
    </ul>
  `,
  data() {
    return {
      todoList: []
    }
  }
}).mount('#app');

利用 computed

let app = Vue.createApp({
  template: `
    <ul v-if="listHasItem">
      <li
        v-for="todo of todoList" :key="todo.id">
        {{ todo.content }}</li>
    </ul>
  `,
  computed: {
    listHasItem() {
      return this.todoList.length > 0;
    }
  },
  data() {
    return {
      todoList: []
    }
  }
}).mount('#app');

Vue2 → v-for 比 v-if 优先级高

探究 render 函数

我们还是来查看 render 函数看看内部如何处理优先级的:

let vm = new Vue({
  el: '#app',
  template: `
    <div>
      <ul>
        <li
          v-for="todo of todoList" :key="todo.id"
          v-if="todo.completed"
        >
          {{ todo.content }}
        </li>
      </ul>
    </div>
  `,
  data() {
    return {
      todoList: [
        {
          id: 1,
          content: 'CONTENT 1',
          completed: true
        },
        {
          id: 2,
          content: 'CONTENT 2',
          completed: false
        },
        {
          id: 3,
          content: 'CONTENT 3',
          completed: true
        }
      ]
    }
  }
}).$mount();
console.log(vm.$options.render);

Untitled

单击打印的函数 f 进去看看:

Untitled

就会发现它跟 Vue3 不同

Vue2 是:

  1. 先遍历 todoList
  2. 而后过滤 completed 状态的

v-if 和 v-for 作用在一个元素上使用时,为什么 Vue3 的优先级逻辑与 Vue2 完全相反

从逻辑上看

从性能上看

其实都不用想,如果先走遍历,就是:

  1. 先把所有元素都渲染了(先遍历),
  2. 再考虑把哪些已渲染的删除(后过滤)

明显性能要低于:

  1. 先看哪些要渲染(先过滤)
  2. 再去把过滤出来的都渲染上(后渲染)