🧑💻 寫在開頭
點贊 + 收藏 === 學會🤣🤣🤣
在開發自定義 Swiper 或長列表組件時,為了優化性能,我們通常會給每一項加上懶加載邏輯:
<view class="item">
<template v-if="shouldRender">
<slot :name="'slot-' + index" />
</template>
</view>
神奇的事情發生了: 哪怕 shouldRender 是 false,打開小程序調試器一看,WXML 樹裏依然排滿了 view slot="slot-0"、view slot="slot-1"... 裏面甚至還塞滿了圖片節點。
結論:你的 v-if 只是“隱藏”了視覺,內存和 DOM 壓力一點沒減。
一、 具名插槽是“物理坑位”
為什麼 v-if 失效了?因為在微信小程序底層,具名插槽(Named Slots) 的實現邏輯是靜態枚舉。
- 預編譯挖坑:小程序原生不支持動態插槽名。Uni-app 編譯器為了兼容 Vue,會根據你的循環邏輯,在 WXML 裏預先寫死所有的佔位符(如
slot="d-0",slot="d-1")。 - 渲染權倒置:在具名插槽模式下,父組件擁有內容的實例化權。父組件會先把所有內容節點生成好,然後再“分發”給子組件。
- 隔離失敗:子組件裏的
v-if只能決定子組件自己是否顯示,但擋不住父組件已經產生的物理節點。這就好比雖然你把家門關了(v-if=false),但鄰居已經把貨卸在了你門口(DOM 佔位) 。
二、 作用域插槽是“動態模版”
要實現真正的懶加載(隨 v-if 銷燬 DOM),必須重構為作用域插槽(Scoped Slots) 。
1. 子組件改造:變“多坑”為“單模板”
不要再給插槽起動態名字,統一使用帶作用域的默認插槽或具名插槽。
<view v-for="(item, index) in list" :key="index">
<template v-if="shouldRender(index)">
<slot name="content" :item="item" />
</template>
</view>
2. 父組件調用:禁止使用 v-for 填坑
這是最關鍵的——父組件不再負責循環產生節點,只提供渲染模板。
<Swiper :list="dataList">
<template v-slot:content="{ item }">
<image :src="item.url" />
</template>
</Swiper>
三、 循環 slot 不會重名衝突嗎?
很多同學看到這裏會問: “子組件循環 20 次 slot name="content",在 WXML 裏難道不會報 ID 衝突或渲染覆蓋嗎?”
真相是: 當你使用“作用域插槽”時,底層渲染邏輯發生了本質改變。
- 具名插槽(舊) :編譯器會生成
d-0,d-1... 等多個不同的物理坑位。 - 作用域插槽(新) :編譯器會將插槽內容封裝成一個微信小程序原生的
template。在 WXML 層面,子組件循環的是同一個“模板調用”,而不是多個“物理坑位”。
這就好比:具名插槽是蓋好了 20 間空房子等分發;而作用域插槽是給了一張圖紙,子組件循環 20 次,只有遇到 v-if="true" 時才照着圖紙現蓋一間房。既然是現蓋,自然不存在重名搶坑的問題。
四、 為什麼換成作用域插槽就生效了?
這是底層架構的質變:
- 實例化時機改變:實例化內容的控制權移交給了子組件。
- 按需生成:只有子組件執行到
<slot />那一行代碼時,父組件定義的“模板”才會動態轉變為真正的 DOM。 - 地基都沒了:如果
v-if="false",父組件的內容在內存裏連影子都不會出現。
五、 避坑小結
如果你在 Uni-app 開發者工具裏發現 DOM 節點數不對勁,請檢查以下兩點:
- 是否在循環裏用了動態具名插槽? (
:name="'xxx' + index" - 父組件是否也寫了
v-for去填坑? 確保父組件只提供<template v-slot:xxx>聲明。