最新情况(截至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-forv-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这个原生组件

  1. 提供v-for+ slot的组件,正常写就行了。比如CommonList,内容不变
  2. 在使用组件的页面(或组件)中,使用微信虚拟节点完成渲染。那么我们需要先搞一个虚拟节点组件,假设叫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这么大的院子,竟然没有办法搞定这个事情??

抱着这个想法,还是决定看看究竟是什么地方出了问题

看看微信小程序本身

通过查找官方材料,我们发现:

  1. 本身在小程序中,slot就有很多相比于h5的不灵活,比如
    组件 wxml 的 slot 中就可以看到,一个组件中要使用多个slot,必须进行配置
  2. 小程序中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版本处理的
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐