在uniapp开发微信小程序时,v-for中使用slot的正确姿势
使用uniapp开发微信小程序,v-for中使用slot的正确方法
最新情况(截至2024-03)
经过确认,HBuilderX 3.8.12+,已经可以不用按照本文内容进行手动处理 generic:scoped-slots-itemView="my-component"
这个操作了。大家可以放心使用。此版本编译后的wxml文件,已经自动生成了上述内容。
经过验证,本文中所述方法:
<template>
<view>
。。其他内容。。。。
<CommonList>
<MyComponent slot="itemView" slot-scope="{item}" :item="item"></MyComponent>
</CommonList>
</view>
</template>
......
</script>
或者
<template>
<view>
<CommonList>
<view slot="itemView" slot-scope="{item}">
只显示ID:{{item.id}}
</view>
</CommonList>
</view>
</template>
......
</script>
都是能正确编译成generic:scoped-slots-itemView
这种形式。
PS:为什么用Vue2的老式写法,因为我发现用v-slot或者Vue3的写法,都会有各种灵异问题,这里就不赘述了,请大家自行考虑怎么用吧。
背景
众所周知,大部分使用uniapp开发微信小程序的同学,一定会遇到使用vue开开心心写一个v-for
,然后v-for
中搞一个slot
,结果在使用的时候,H5没问题,微信小程序就只渲染第一个的问题。
比如我有一个组件,假设叫CommonList
这个组件里面有一个v-for
,v-for
中有一个slot
,它循环渲染处理products 。
<template>
。。其他内容。。。。
<view class="lists">
<view class="data-item" v-for="(item, index) in products" :key="index">
<text>标题</text>
<slot name="itemView" :item="item">
<view>
<text>{{item}}</text>
</view>
</slot>
</view>
</view>
。。其他内容。。。。
</template>
现在我有一个页面,需要要使用这个组件
<template>
<view>
。。其他内容。。。。
<CommonList>
<view slot="itemView" slot-scope="{item}">
只显示ID:{{item.id}}
</view>
</CommonList>
</view>
</template
<script>
import CommonList from './commonlist'
export default {
components: { CommonList }
}
</script>
那么,不出意外的话,在H5中,程序会正常,但是在小程序中,不管上面的products有几个,只会渲染第一个(<text>标题</text>会正常渲染,因为它不是slot)
如果在v-for中不能使用slot,那么对于开发来说,绝对是一个痛苦!大家应该很清楚
先说结论(有兴趣的可以看后面的折腾过程)
=================
v-for + slot于微信小程序,需要使用 小程序虚拟组件 进行slot渲染
=================
以CommonList
举例,在CommonList中,v-for是作用于view这个原生组件
- 提供
v-for
+slot
的组件,正常写就行了。比如CommonList
,内容不变 - 在使用组件的页面(或组件)中,使用
微信虚拟节点
完成渲染。那么我们需要先搞一个虚拟节点组件,假设叫MyComponent
(需要注意,这个MyComponent应该有一个Props来接受slot的参数),然后,在H5的时候正常使用,如果是微信小程序,使用generic:xxxx
方式并配合一个特殊的slot完成渲染
:这是改造后的使用CommonList组件的页面
<template>
<view>
。。其他内容。。。。
<CommonList generic:scoped-slots-itemView="my-component">
<!-- #ifdef mp-weixin -->
<!-- 特殊的slot,主要是让编译后的wxml能够正确处理(可以看后面的分析) -->
<view slot="itemView" style="display: none">
<!-- #endif -->
<!-- #ifdef H5 -->
<MyComponent slot="itemView" slot-scope="{item}" :item="item"></MyComponent>
<!-- #endif -->
</CommonList>
</view>
</template
<script>
import CommonList from './commonlist'
import MyComponent'./my-component'
export default {
components: { CommonList, MyComponent}
}
</script>
:这是MyComponent
<template>
<view>
。。。。其他内容。。。。
<view>只显示ID:{{item.id}}</view>
。。。。其他内容。。。。
</view>
</template
<script>
。。。。其他内容。。。。
export default {
name: 'FeeListItem',
props: ['item'], //特别注意这个,这是接受slot参数的属性
}
。。。。其他内容。。。。
</script>
重点就是
1、组件上使用 `generic:scoped-slots-itemView=“my-component”
2、使用<view slot="itemView" style="display: none">
以上内容中:
generic:
表示使用虚拟节点,就这么写就行了
scoped-slos-itemView
是uniapp编译后的特定内容,也就是scoped-slots-
+v-for中slot的名字
"my-component"
是虚拟组件的uniapp编译后,对应json文件中的组件申明,注意写法(做vue的都懂)
<view slot="itemView" style="display: none">
主要是让uniapp编译后的wxml中,wx:if="{{$slot.itemView}}"起效(用任何控件都可以)
======================折腾过程==========================
分析
在网络上搜索"uniapp 小程序v-for slot",会发现官方沉默。
难道uniapp这么大的院子,竟然没有办法搞定这个事情??
抱着这个想法,还是决定看看究竟是什么地方出了问题
看看微信小程序本身
通过查找官方材料,我们发现:
- 本身在小程序中,slot就有很多相比于h5的不灵活,比如
组件 wxml 的 slot 中就可以看到,一个组件中要使用多个slot,必须进行配置 - 小程序中wx:for本身就不支持循环渲染slot,可以搜索到很多和 slot能否配合wx:for使用? 类似的问题
又是类似的自问:难道微信这么大个院子,不知道代码不能循环分发导致的效率和耦合问题?
编译后的小程序代码
从我截取的片段来看
<view class="data-v-282ea8d0">
<block wx:if="{{$slots.itemView}}">
<slot name="itemView"></slot>
<scoped-slots-itemView item="{{item.$orig}}" class="scoped-ref"></scoped-slots-itemView>
</block>
<block wx:else>
<view class="data-v-282ea8d0"><text class="data-v-282ea8d0">{{item.$orig}}</text></view>
</block>
</view>
这里就可以看到,slot
确实被编译出来了,但是由于上面的情况,微信本身不支持wx:for
中渲染N个slot
。所以我们的组件在使用的时候会出问题
但是呢,我们又发现,它多了一个
<scoped-slots-itemView item="{{item.$orig}}" class="scoped-ref"></scoped-slots-itemView>
这是个什么玩意儿?
微信小程序虚拟节点
从官方文档的 这里:抽象节点 我们可以知道,其实微信把组件控制的比较死,每个组件的slot也是只为组件本身量身而作(可以说是只能给装备打物理孔,不允许打逻[魔]辑[法]孔),所以,它搞了一个虚拟组件。
虚拟组件本身是组件,不会受到v-for + slot
影响。那么如果把数据传递给v-for
中的虚拟组件,然后虚拟组件中使用slot
,就可以完成之前的工作了。
微信小程序中使用虚拟节点+自定义组件
看了抽象节点的主要逻辑,我们可以总结出:
1、组件中必须声明虚拟节点,比如上面那个CommonList
,如果是用微信原生开发的话
。。。
<block wx:for="products" >
<itemView>。。。。</itemView>
</block>
。。。
对应的CommonList.json中需要声明虚拟节点
{
。。。
"componentGenerics": {
"itemView": true
}
}
2、使用组件的时候,需要注意
。。。
<CommnList generic:itemView="你的组件名">
。。。
这下一看,刚才那个scoped-slots-itemView
不就可能是虚拟节点吗?
借着看了看编译后的CommonList.json
,可以看到
"component": true,
"componentGenerics": {
"scoped-slots-itemView": true
}
看来uniapp在编译的时候,把v-for
中的slot
搞成了虚拟节点
==========================================
在Uniapp中正确渲染虚拟节点
有了上面的官方技术支持,那么我们就可以考虑如下处理:
在vue代码中,直接在使用组件的地方写generic:xxxxxx=yyyyy
那么页面代码就变为:
。。其他内容。。。。
<CommonList generic:scoped-slots-itemView="my-component">
</CommonList >
。。其他内容。。。。
<script>
import CommonList from './commonlist'
import MyComponent'./my-component'
export default {
components: { CommonList, MyComponent}
}
</script>
这里my-component
就是虚拟组件,而scoped-slots-itemView
就是根据编译后的wxml的中的虚拟节点名得到的,其实观察一下就可以发现,它的规则是:scoped-slots-
+v-for中slot的名字
写完以后编译,查看编译后的页面,发现其对应的wxml中,common-list组件确实有了generic:scoped-slots-itemView="my-component"
这个属性,并且在json中也能看到对应的my-component组件声明
好了,直接编译运行到小程序模拟器,结果,发现my-component组件并没有加载出来
有了上面的结论,我们为什么没有在小程序中看到正确的结果呢??
明明条件都已经具备了:
1、编译后的CommonList.json文件中声明了虚拟节点
2、编译后的CommonList.wxml中,wx:for中包含了虚拟节点
3、使用组件的页面中,编译后也看到了对应的虚拟节点属性和声明
继续分析我们可以看到,在前面的 编译后的小程序代码 一节中,页面编译出来block有一个wx:if="{{$slot.itemView}}"
,从逻辑上看,就是这个if导致了没有进入虚拟节点渲染
这个$slot.itemView其实就是编译后的slot组件,所以我们需要让这个if通过,就必须加入一个控件来作为slot,但是这个组件一旦加入,就会渲染出来,所以考虑用一个display:none来处理
于是,最终的代码就是:
。。其他内容。。。。
<CommonList generic:scoped-slots-itemView="my-component">
<view slot="itemView" style="display: none"></view>
<CommonList>
。。其他内容。。。。
<script>
import CommonList from './commonlist'
import MyComponent'./my-component'
export default {
components: { CommonList, MyComponent}
}
</script>
编译后运行,效果正确!MyComponent组件成功渲染。
那么如果是H5和小程序要多端编译怎么办呢,加入条件编译即可
<template>
<view>
。。其他内容。。。。
<CommonList generic:scoped-slots-itemView="my-component">
<!-- #ifdef mp-weixin -->
<!-- 特殊的slot,主要是让编译后的wxml能够正确处理(可以看后面的分析) -->
<view slot="itemView" style="display: none">
<!-- #endif -->
<!-- #ifdef H5 -->
<MyComponent slot="itemView" slot-scope="{item}"></MyComponent>
</CommonList>
</view>
</template
<script>
import CommonList from './commonlist'
import MyComponent'./my-component'
export default {
components: { CommonList, MyComponent}
}
</script>
PS: 所有的处理都是在HBuilderX v3.8.7版本处理的
更多推荐
所有评论(0)