后续源码分析都是依据 "version": "2.7.16" (vue2版本【最新版本】)和 "version": "3.5.12"【当前最新版本】 (vue3版本)分析的对比
问题发生
一般指的是 v-for 和 v-if 连用的情况
结论
v-if 和 v-for 的优先级主要体现在它们的渲染逻辑中
- Vue2中
v-for的优先级高于v-if - Vue3中
v-if的优先级高于v-for - 两种写在一起的写法均不被官方推荐 (每次渲染都会先循环再进行条件判断)
Vue 3 的改进
Vue 3 通过改变这种优先级,使得开发者在使用 v-if 和 v-for 时,能够更清晰地理解条件渲染的逻辑。这样在 v-if 为 false 时,相关节点不会被渲染,从而避免了访问未定义变量的问题。
主要区别
- Vue 2:
v-for优先于v-if,可能导致潜在的错误。 - Vue 3:
v-if优先于v-for,提供更健壮的条件渲染逻辑。
总结
总的来说,v-for 的优先级高于 v-if,在实际使用中应通过结构上的优化来提高渲染性能。
vue2中体现
源码相关位置 packages\server-renderer\src\optimizing-compiler\codegen.ts
function elementToSegments(el, state): Array<StringSegment> {
// v-for / v-if
// 通过检查 `el.for` 和 `!el.forProcessed`,如果当前元素有 `v-for` 指令且尚未被处理,就会执行 `genFor` 函数来生成循环内容
if (el.for && !el.forProcessed) {
el.forProcessed = true
return [
{
type: EXPRESSION,
value: genFor(el, state, elementToString, '_ssrList')
}
]
} else if (el.if && !el.ifProcessed) {
el.ifProcessed = true
return [
{
type: EXPRESSION,
value: genIf(el, state, elementToString, '"<!---->"')
}
]
} else if (el.tag === 'template') {
return childrenToSegments(el, state)
}
const openSegments = elementToOpenTagSegments(el, state)
const childrenSegments = childrenToSegments(el, state)
const { isUnaryTag } = state.options
const close =
isUnaryTag && isUnaryTag(el.tag)
? []
: [{ type: RAW, value: `</${el.tag}>` }]
return openSegments.concat(childrenSegments, close)
}
export function genElement(el: ASTElement, state: CodegenState): string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
} else {
// component or element
let code
if (el.component) {
code = genComponent(el.component, el, state)
} else {
let data
const maybeComponent = state.maybeComponent(el)
if (!el.plain || (el.pre && maybeComponent)) {
data = genData(el, state)
}
let tag: string | undefined
// check if this is a component in <script setup>
const bindings = state.options.bindings
if (maybeComponent && bindings && bindings.__isScriptSetup !== false) {
tag = checkBindingType(bindings, el.tag)
}
if (!tag) tag = `'${el.tag}'`
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c(${tag}${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`
}
// module transforms
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}
return code
}
}
vue3中体现
在 createIfBranch 函数中,如果节点同时存在 v-if 和 v-for,则首先会检测 v-for 指令的存在(通过 findDir(node, 'for'))。如果存在 v-for,处理逻辑会收紧为将整个 v-for 循环作为子节点,即使有多个条件分支。这样,在 Vue 渲染过程中,可以确保 v-for 在逻辑上优先于 v-if,同时生成的条件分支可以动态的决定是否渲染。
function createIfBranch(node: ElementNode, dir: DirectiveNode): IfBranchNode {
const isTemplateIf = node.tagType === ElementTypes.TEMPLATE
return {
type: NodeTypes.IF_BRANCH,
loc: node.loc,
condition: dir.name === 'else' ? undefined : dir.exp,
children: isTemplateIf && !findDir(node, 'for') ? node.children : [node],
userKey: findProp(node, `key`),
isTemplateIf,
}
}
render函数--> 虚拟DOM -->真实DOM 这样的过程,呈现到页面当中
vue2编译成的render函数 v2.template-explorer网址
vue3编译成的render函数 vue3-template-explorer网址
在 Vue 2 和 Vue 3 中,优化 v-if 和 v-for 同时出现的情况可以通过不同的方式实现,以下是一些具体的优化示例。
Vue 2 的优化示例
假设我们有一个包含用户信息的列表,并且我们只想渲染状态为“可见”的用户。以下是一个可能的 Vue 2 组件示例:
<template>
<ul>
<li v-for="user in users" :key="user.id" v-if="user.visible">
{{ user.name }}
</li>
</ul>
</template>
<script>
export default {
data() {
return {
users: [
{ id: 1, name: 'Alice', visible: true },
{ id: 2, name: 'Bob', visible: false },
{ id: 3, name: 'Charlie', visible: true },
],
};
},
};
</script>
优化建议:可以首先过滤出可见的用户,然后再进行循环,从而避免在 DOM 中无用的占位元素。
<template>
<ul>
<li v-for="user in visibleUsers" :key="user.id">
{{ user.name }}
</li>
</ul>
</template>
<script>
export default {
data() {
return {
users: [
{ id: 1, name: 'Alice', visible: true },
{ id: 2, name: 'Bob', visible: false },
{ id: 3, name: 'Charlie', visible: true },
],
};
},
computed: {
visibleUsers() {
return this.users.filter(user => user.visible);
},
},
};
</script>
通过这种方式,我们只遍历可见的用户,有效减少了不必要的渲染计算。
Vue 3 的优化示例
在 Vue 3 中,我们同样关注 v-if 和 v-for 的使用,但通过 Vue 3 的更优编译机制,可以进一步优化。例如:
<template>
<ul>
<li v-for="user in users" :key="user.id" v-if="user.visible">
{{ user.name }}
</li>
</ul>
</template>
<script setup>
import { ref, computed } from 'vue';
const users = ref([
{ id: 1, name: 'Alice', visible: true },
{ id: 2, name: 'Bob', visible: false },
{ id: 3, name: 'Charlie', visible: true },
]);
const visibleUsers = computed(() => {
return users.value.filter(user => user.visible);
});
</script>
在 Vue 3 中,使用 setup 语法糖,我们可以利用 computed 属性来过滤用户。这种方式更加清晰,并且在 Vue 3 的编译器内部,处理方式也更为高效。
总结
- 过滤而不是条件渲染:在
v-if与v-for同时使用时,优先使用计算属性或过滤逻辑,避免在循环中使用条件判断。 - 利用
computed:通过计算属性来维护可见用户的列表,使得渲染逻辑更加清晰并提高性能。 - 选择适当的渲染函数:在 Vue 3 中的实现让编译器能更聪明地处理渲染,利用
setup和computed的组合,进一步提高了性能。