Uni-app 性能天坑:为什么 v-if 删不掉 DOM 节点
<h1 data-id="heading-0">🧑💻 写在开头</h1><p>点赞 + 收藏 === 学会🤣🤣🤣</p>
<p>在开发自定义 Swiper 或长列表组件时,为了优化性能,我们通常会给每一项加上懒加载逻辑:</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;"><view class="item">
<template v-if="shouldRender">
<slot :name="'slot-' + index" />
</template>
</view></pre>
</div>
<div>
<div>
<p><strong>神奇的事情发生了:</strong> 哪怕 <code>shouldRender</code> 是 <code>false</code>,打开小程序调试器一看,WXML 树里依然排满了 <code>view slot="slot-0"</code>、<code>view slot="slot-1"</code>... 里面甚至还塞满了图片节点。</p>
<p><strong>结论:你的 v-if 只是“隐藏”了视觉,内存和 DOM 压力一点没减。</strong></p>
<hr>
<h3 data-id="heading-0">一、 具名插槽是“物理坑位”</h3>
<p>为什么 <code>v-if</code> 失效了?因为在微信小程序底层,<strong>具名插槽(Named Slots)</strong> 的实现逻辑是<strong>静态枚举</strong>。</p>
<ol>
<li><strong>预编译挖坑</strong>:小程序原生不支持动态插槽名。Uni-app 编译器为了兼容 Vue,会根据你的循环逻辑,在 WXML 里预先写死所有的占位符(如 <code>slot="d-0"</code>, <code>slot="d-1"</code>)。</li>
<li><strong>渲染权倒置</strong>:在具名插槽模式下,<strong>父组件拥有内容的实例化权</strong>。父组件会先把所有内容节点生成好,然后再“分发”给子组件。</li>
<li><strong>隔离失败</strong>:子组件里的 <code>v-if</code> 只能决定子组件自己是否显示,但挡不住父组件已经产生的物理节点。这就好比<strong>虽然你把家门关了(v-if=false),但邻居已经把货卸在了你门口(DOM 占位)</strong> 。</li>
</ol><hr>
<h3 data-id="heading-1">二、 作用域插槽是“动态模版”</h3>
<p>要实现真正的懒加载(随 <code>v-if</code> 销毁 DOM),必须重构为<strong>作用域插槽(Scoped Slots)</strong> 。</p>
<h4 data-id="heading-2">1. 子组件改造:变“多坑”为“单模板”</h4>
<p>不要再给插槽起动态名字,统一使用带作用域的默认插槽或具名插槽。</p>
</div>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;"><view v-for="(item, index) in list" :key="index">
<template v-if="shouldRender(index)">
<slot name="content" :item="item" />
</template>
</view></pre>
</div>
<h4 data-id="heading-3">2. 父组件调用:禁止使用 v-for 填坑</h4>
<p>这是最关键的——父组件不再负责循环产生节点,只提供渲染模板。</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;"><Swiper :list="dataList">
<template v-slot:content="{ item }">
<image :src="item.url" />
</template>
</Swiper></pre>
</div>
<div>
<div>
<h3 data-id="heading-4">三、 循环 slot 不会重名冲突吗?</h3>
<p>很多同学看到这里会问: <strong>“子组件循环 20 次 <code>slot name="content"</code>,在 WXML 里难道不会报 ID 冲突或渲染覆盖吗?”</strong></p>
<p><strong>真相是:</strong> 当你使用“作用域插槽”时,底层渲染逻辑发生了本质改变。</p>
<ul>
<li><strong>具名插槽(旧)</strong> :编译器会生成 <code>d-0</code>, <code>d-1</code>... 等<strong>多个不同的物理坑位</strong>。</li>
<li><strong>作用域插槽(新)</strong> :编译器会将插槽内容封装成一个<strong>微信小程序原生的 <code>template</code></strong>。在 WXML 层面,子组件循环的是同一个“模板调用”,而不是多个“物理坑位”。</li>
</ul>
<p>这就好比:具名插槽是盖好了 20 间空房子等分发;而作用域插槽是给了一张图纸,子组件循环 20 次,只有遇到 <code>v-if="true"</code> 时才照着图纸现盖一间房。<strong>既然是现盖,自然不存在重名抢坑的问题。</strong></p>
<hr>
<h3 data-id="heading-5">四、 为什么换成作用域插槽就生效了?</h3>
<p>这是底层架构的质变:</p>
<ul>
<li><strong>实例化时机改变</strong>:实例化内容的控制权移交给了<strong>子组件</strong>。</li>
<li><strong>按需生成</strong>:只有子组件执行到 <code><slot /></code> 那一行代码时,父组件定义的“模板”才会动态转变为真正的 DOM。</li>
<li><strong>地基都没了</strong>:如果 <code>v-if="false"</code>,父组件的内容在内存里连影子都不会出现。</li>
</ul>
<hr>
<h3 data-id="heading-6">五、 避坑小结</h3>
<p>如果你在 Uni-app 开发者工具里发现 DOM 节点数不对劲,请检查以下两点:</p>
<ol>
<li><strong>是否在循环里用了动态具名插槽?</strong> (<code>:name="'xxx' + index"</code></li>
<li><strong>父组件是否也写了 <code>v-for</code> 去填坑?</strong> 确保父组件只提供 <code><template v-slot:xxx></code> 声明。</li>
</ol></div>
</div>
<div>
<h3 id="tid-D8HBxE">如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。</h3>
</div>
<p><em><img src="https://img2024.cnblogs.com/blog/2149129/202501/2149129-20250122165814748-630765389.png" alt="" loading="lazy"></em></p><br><br>
来源:https://www.cnblogs.com/smileZAZ/p/19523993
頁:
[1]