slot插槽是微信小程序自定义组件里非常核心的一个能力,它让组件的使用方可以把自定义的内容塞进组件内部。而slot的默认内容,也就是当调用方不传任何子节点时组件展示的兜底内容,在很多业务场景里都需要根据条件来决定显示还是隐藏。比如一个卡片组件,没有传入插槽内容时默认展示占位图,但占位图本身可能只在某些页面状态下才需要出现。这篇文章就来详细拆解slot默认内容的条件渲染方案。

先弄清楚slot默认内容的触发机制
要给slot默认内容做条件渲染,第一步得理解小程序里默认内容到底什么时候会显示。在自定义组件的wxml中,写在<slot>标签内部的内容就是默认内容。当组件的调用方在组件标签内没有放置任何子节点时,slot标签内部包裹的默认内容就会被渲染出来;一旦调用方传入了哪怕一个子节点,默认内容就会被完全替换掉,不会再参与渲染。
这个机制有一个容易被忽视的细节:默认内容的显示和隐藏是由框架自动判断的,判断依据只有“调用方是否传入了子节点”这一条,跟传入的内容本身是否为空、是否可见都没有关系。比如调用方传了一个带wx:if的节点进来,即使这个节点因为条件不满足而没有渲染,slot依然会被认为“有传入内容”,默认内容同样不会显示。这一点是很多条件渲染问题的根源。
看一个基础例子,组件内部的wxml如下:
<!-- components/card/card.wxml -->
<view class="card">
<slot>
<view class="placeholder">这里是默认占位内容</view>
</slot>
</view>当页面这样使用组件时,默认内容会显示:
<card />
而只要传入了子节点,哪怕子节点渲染结果为空,默认内容也不会显示:
<card>
<view wx:if="{{false}}">条件不满足</view>
</card>正因为框架的兜底逻辑无法被业务逻辑干预,所以当默认内容本身需要条件控制时,我们必须把条件写在自己可控的地方,也就是组件自身的data或properties上。
用wx:if直接控制默认内容的显隐
最直接的方案,是在slot的默认内容上加wx:if,条件来源于组件自身的data。这样组件内部可以在合适的时机修改data,从而决定默认内容是否渲染。这种做法的优点是改动最小,只需要调整组件wxml和js,调用方完全无感知。
举个例子,一个列表组件在没有插槽内容且数据加载完成时才展示“暂无数据”的默认提示:
<!-- components/empty-list/empty-list.wxml -->
<view class="list-wrap">
<slot>
<view class="empty-tip" wx:if="{{showDefault && loaded}}">
<image src="/images/empty.png" mode="aspectFit"/>
<text>暂时没有数据哦</text>
</view>
</slot>
</view>
// components/empty-list/empty-list.js
Component({
data: {
showDefault: true,
loaded: false
},
lifetimes: {
attached() {
// 模拟异步请求,请求完成后才允许显示默认内容
setTimeout(() => {
this.setData({ loaded: true });
}, 1000);
}
},
methods: {
hideDefault() {
this.setData({ showDefault: false });
}
}
});这里要注意wx:if和hidden的区别。如果默认内容里有图片或列表这种较重的节点,用wx:if可以在条件为假时彻底不渲染,节省渲染开销;如果默认内容需要频繁切换显隐,用hidden控制样式会更划算,因为它不会反复销毁和重建节点。默认内容属于兜底展示,切换频率一般不高,所以wx:if是更常见的选择。
通过properties让调用方控制默认内容
组件内部的data只有组件自己能改,更多时候我们希望由调用方来决定默认内容是否展示。这时可以把控制条件设计成一个property,调用方在标签上直接传值。这种方式让组件的复用性更强,不同页面可以有不同的默认展示策略。
<!-- components/card/card.wxml -->
<view class="card">
<slot>
<view wx:if="{{showPlaceholder}}" class="card-placeholder">
<text>请传入自定义内容</text>
</view>
</slot>
</view>
// components/card/card.js
Component({
properties: {
// 是否展示插槽默认内容,默认为true
showPlaceholder: {
type: Boolean,
value: true
}
}
});调用方的使用方式就非常灵活了:
<!-- 页面A:需要默认占位 -->
<card />
<!-- 页面B:即使不传内容也不要占位 -->
<card show-placeholder="{{false}}" />
<!-- 页面C:传入自定义内容,默认内容自动被替换 -->
<card>
<view>这是自定义卡片内容</view>
</card>如果条件比较复杂,比如默认内容的显隐依赖多个外部状态,还可以配合observers做联动。当某个property变化时,在观察器里做业务判断,再把结果写入一个内部data字段,wxml里绑定这个内部字段即可。这样把判断逻辑收敛在组件内部,调用方只需要传最原始的状态,代码可读性会好很多。
// 组件js:通过observers联动多个属性
Component({
properties: {
status: String,
isVip: Boolean
},
data: {
canShowDefault: false
},
observers: {
'status, isVip': function (status, isVip) {
// 只有状态为空且非VIP用户时才展示默认引导内容
this.setData({
canShowDefault: status === '' && !isVip
});
}
}
});多插槽场景下的注意事项和常见坑
当组件开启了options: { multipleSlots: true }后,可以定义多个具名插槽,每个插槽都可以有自己的默认内容。多插槽下的条件渲染逻辑和单插槽一致,但有几个坑需要提前知道。
第一个坑是空格和纯文本节点也会被当成“传入了内容”。如果调用方在组件标签里写了换行或者一段看似空的文本,默认内容就不会显示。排查这类问题时可以先把组件标签写成自闭合形式验证一下,确认是内容判断问题后再回头检查传入了什么。
// 多插槽组件定义
Component({
options: {
multipleSlots: true
},
properties: {
showHeaderDefault: {
type: Boolean,
value: true
}
}
});
<!-- 多插槽组件wxml -->
<view>
<slot name="header">
<view wx:if="{{showHeaderDefault}}" class="default-header">默认头部</view>
</slot>
<slot name="body">
<view class="default-body">默认正文</view>
</slot>
</view>第二个坑是默认内容的样式作用域。默认内容写在组件内部,它的样式受组件的wxss控制,而调用方传进来的内容样式由页面决定。如果默认内容和传入内容需要保持一致的视觉表现,建议把公共样式抽到组件里,默认内容直接用,同时对外暴露class外部样式类(externalClasses)给调用方,保证两种来源的内容渲染效果统一。
第三个坑是嵌套组件的场景。如果slot默认内容里又引用了另一个自定义组件,注意子组件的生命周期依然会正常触发,即使后续被wx:if隐藏销毁也不会撤销已经发出的请求。涉及轮询、定时器的默认内容,一定要在detached生命周期里做好清理,避免内存泄漏。
总的来说,slot默认内容的条件渲染核心思路就是:框架的兜底机制管“有没有传入内容”,业务条件交给wx:if结合properties或data来管。把这两个层面分开处理,再复杂的显隐需求都能拆得清清楚楚。