首先,官方不推荐v-if和v-for在同一个元素上使用。其次,如果两者同时使用,v-if和v-for的优先级怎么确定?在vue2和vue3中这两者的优先级顺序不一样。vue2是v-for优先,条件不存在时也会渲染多个注释节点。在vue3中进行了改进,v-if优先级高。在vue3中,v-if在编译阶段进行了静态节点提升,所以在v-for遍历每个节点,v-if对单个节点判断,这种情况会报错。不管是vue2还是vue3,都推荐,将v-for遍历的数组用计算属性改写,根据v-if依赖的条件返回过滤后的数组。
在介绍v-if和v-for的时候,最直观的方式就是去官方提供的模板编译网站,看v-if和v-for同时使用后编译成的结果。
vue2的模板地址:Vue Template Explorer
vue3的模板编译地址:Vue Template Explorer
vue2测试
在li标签上同时使用v-for和v-if
<template>
<div>
<li v-for="item in arr" :key="item" v-if="exists"></li>
</div>
</template>
<script>
export default {
data() {
return {
arr: [1, 2, 3, 4],
exists: false,
};
},
};
</script>
<style></style>
编译就会报错,让你把v-if移到上面标签
如果v-if使用了v-for遍历的item。编译器会提示你使用计算属性过滤满足条件的数组
vue2模板编译
v-if和v-for同时存在
OK,借用vue2的模板编译,看下代码编译后的结果。
function render() {
with(this) {
return _c('div', _l((arr), function (item) {
return (exists) ? _c('li', {
key: item
}) : _e()
}), 0)
}
}
根据
arr
数组的长度和exists
的值,渲染一个由<li>
元素组成的列表。如果exists
的值为假,则不渲染任何列表元素。因此是先遍历数组,在判断每项。即使不存在,也会创建一个空的注释节点。注释没有内容。
v-if引用了v-for遍历的item内容
<div>
<li v-for="item in arr" :key="item" v-if="item % 2">item</li>
</div>
得到如下结果。可以看到,在vue2中是支持v-if和v-for的同时存在。先循环,在对item%2进行逻辑判断。
function render() {
with(this) {
return _c('div', _l((arr), function (item) {
return (item % 2) ? _c('li', {
key: item
}, [_v("item")]) : _e()
}), 0)
}
}
with(this) { ... }
:这段代码用于在渲染函数中使用this
关键字,可以简化对组件实例属性的访问。_c('div')
:这段代码用于创建一个div
元素,返回一个虚拟 DOM 节点。_l((arr), function (item) { ... })
:这段代码用于创建一个包含多个li
元素的数组,每个li
元素对应arr
数组的一个元素。其中arr
是一个组件实例的属性,表示要渲染的数组。(item % 2)
:这段代码用于判断当前item
的值是否能被 2 整除,返回一个布尔值。_c('li', { key: item })
:这段代码用于创建一个li
元素,并设置key
属性为当前item
的值。_v("item")
:这段代码用于创建一个文本节点,其值为字符串 "item"。_e()
:这段代码用于创建一个空的虚拟 DOM 节点,表示当前item
不需要渲染为li
元素。0
:这个数字表示在div
元素中渲染li
元素的位置,这里表示在第一个位置。
vue3测试
v-if引用了v-for遍历的item内容
直接举例v-if和v-for同时存在,并且v-if引用了v-for的遍历结果。可以看到控制台报错,item未定义。这是因为item在编译为渲染函数的时候提到v-for的上面一层了。使用的时候还没在定义。
<template>
<div>
<li v-for="item in arr" :key="item" v-if="item % 2">item</li>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
const exists = ref(false);
let arr = ref([1, 2, 3, 4]);
</script>
vue3模板编译
可以看到vue3编译后v-if被提到外面。首先会判断
(_ctx.item % 2),这里item未定义,因为item是在v-for定义的,所以会报错。
判断完v-if的条件,假设条件是对的,会执行第一个渲染列表每项。如果条件不满足,创建一个注释节点,与vue2不同,这里注释的内容有一个v-if标识。在 Vue 中很多地方都运用了注释节点来作为占位节点,其目的是在不展示该元素的时候,标识其在页面中的位置,以便在
patch
的时候将该元素放回该位置。
import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, createCommentVNode as _createCommentVNode } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
(_ctx.item % 2)
? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(_ctx.arr, (item) => {
return (_openBlock(), _createElementBlock("li", { key: item }, "item"))
}), 128 /* KEYED_FRAGMENT */))
: _createCommentVNode("v-if", true)
]))
}
(_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(_ctx.arr, (item) => { ... })
这一行的作用是渲染一个列表,它使用_renderList
函数来渲染_ctx.arr
数组中的每个元素。如果当前item
是偶数,则渲染一个li
元素,并将其作为子节点插入到列表中。这里使用了_Fragment
函数,用于将多个子节点合并为一个单一的虚拟节点。_createCommentVNode("v-if", true)
这一行创建一个注释节点,表示在条件不满足的情况下不渲染任何内容。
vue2源码分析
v-if和v-for是不是写在模板template里,那么它们的源码就去模板编译过程去找。编译有三个过程,
parse将模板解析为AST语法树
->optimize优化AST语法树
->codegen生成编译后的code
。在最后codegen
过程中,会先解析AST
树中的与v-for
相关的属性,再解析与v-if
相关的属性。
genElement函数
在源码 vue-main\src\compiler\codegen\index.ts文件中
其实从此处可以初步知道为什么v-for优先级比v-if高,因为解析ast树生成渲染函数代码时,会先解析ast树中涉及到v-for的属性。然后再解析ast树中涉及到v-if的属性。而且genFor在会把el.forProcessed置为true,防止重复解析v-for相关属性。
genFor\genIf函数
vue-main\src\compiler\codegen\index.ts
以之前例子为例,处理
li
的ast
树时,会先调用genElement
,处理到for
属性时,此时forProcessed
为虚值,此时调用genFor
处理li
树中的v-for
相关的属性。然后再调用genElement
处理li
树,此时因为forProcessed
在genFor
中已被标记为true
。因此genFor
不会被执行,继而执行genIf
处理与v-if
相关的属性。
vue3
在vue3中没有那个代码能够明显看出v-if和v-for的优先级
只有这个文件稍微体现了某个处理过程中对v-if和v-for同时存在的处理
在core-main\packages\compiler-core\src\transforms\vIf.ts文件中
createIfBranch函数
在这个函数中,可以看出当
v-if
和v-for
同时使用时,Vue 3会根据条件判断是否为<template>
元素以及是否存在v-for
指令来决定v-if
的条件判断应用在哪个节点上。具体来说,如果节点是<template>
元素且没有v-for
指令,那么v-if
的条件判断会被应用在<template>
元素的子节点上;否则,v-if
的条件判断会被应用在当前节点上。
虽然这段代码没有直接提到“静态提升”,但根据Vue 3的设计,Vue 3会在编译阶段对模板进行静态分析,并对
v-if
进行静态提升,以提高性能。因此,可以理解为在这个函数中,Vue 3会对v-if
进行静态提升,以确保条件判断的准确性和性能优化。
vue2 VS vue3
- 在Vue 2中,由于
v-for
的优先级高于v-if
,当v-if
使用了v-for
的遍历结果时,Vue 2会对每个元素都执行v-if
条件判断。这可能导致性能问题,特别是在数据量较大时。- 而在Vue 3中,由于
v-if
的优先级高于v-for
,Vue 3会在编译阶段对v-if
进行静态提升(static hoisting),只对整个元素进行一次条件判断,而不会对每个元素都执行条件判断。这样可以提高性能,特别是在大型列表渲染时
当v-if
和v-for
同时作用在同一个元素上,并且v-if
使用了v-for
的遍历结果时,两个版本的处理方式有所不同:
vue2会正常渲染。因为vue2先对for展开,再对v-if使用的item进行判断。
而vue3会发出警告。控制台会警告,item未定义。因为vue3先使用了v-if,在使用v-if的时候找不到item。所以尽量不要用v-if分析v-for里的内容。
如何避免同时使用v-if和v-for
为了避免同时使用v-if
和v-for
,可以考虑以下几种方法:
- 使用计算属性或方法:将需要根据条件筛选的数据在组件中提前处理好,然后在模板中只使用
v-for
进行循环展示。 - 使用过滤器:通过过滤器对数据进行筛选,然后在模板中只使用
v-for
进行循环展示。 - 使用嵌套元素:将需要条件判断的元素放置在另一个包裹元素内,然后在外层元素上使用
v-for
,在内层元素上使用v-if
。目的就是让v-if和v-for分开