後續原始碼分析都是依據 "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
的組合,進一步提高了效能。
作者:魚櫻前端
連結:https://juejin.cn/post/7435991972228038671