博客 / 詳情

返回

Uni-app 性能天坑:為什麼 v-if 刪不掉 DOM 節點

🧑‍💻 寫在開頭

點贊 + 收藏 === 學會🤣🤣🤣

在開發自定義 Swiper 或長列表組件時,為了優化性能,我們通常會給每一項加上懶加載邏輯:

<view class="item">
  <template v-if="shouldRender">
    <slot :name="'slot-' + index" />
  </template>
</view>

神奇的事情發生了: 哪怕 shouldRenderfalse,打開小程序調試器一看,WXML 樹裏依然排滿了 view slot="slot-0"view slot="slot-1"... 裏面甚至還塞滿了圖片節點。

結論:你的 v-if 只是“隱藏”了視覺,內存和 DOM 壓力一點沒減。


一、 具名插槽是“物理坑位”

為什麼 v-if 失效了?因為在微信小程序底層,具名插槽(Named Slots) 的實現邏輯是靜態枚舉

  1. 預編譯挖坑:小程序原生不支持動態插槽名。Uni-app 編譯器為了兼容 Vue,會根據你的循環邏輯,在 WXML 裏預先寫死所有的佔位符(如 slot="d-0", slot="d-1")。
  2. 渲染權倒置:在具名插槽模式下,父組件擁有內容的實例化權。父組件會先把所有內容節點生成好,然後再“分發”給子組件。
  3. 隔離失敗:子組件裏的 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 節點數不對勁,請檢查以下兩點:

  1. 是否在循環裏用了動態具名插槽?:name="'xxx' + index"
  2. 父組件是否也寫了 v-for 去填坑? 確保父組件只提供 <template v-slot:xxx> 聲明。

如果對您有所幫助,歡迎您點個關注,我會定時更新技術文檔,大家一起討論學習,一起進步。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.