Slots 穿透方案,跨组件

200 阅读1分钟

Slots 穿透方案-多子组件

方法 1:useSlots()

通常我们封装业务组件时一般不至于一个子组件,但多个子组件的情况下就要特别注意 Slot 命名情况了,这边分享一个在平时开发时我们选择的一个方案:使用不同前缀来区分不同 slotprops 也是同理。在 ProCard.vue 中我们加入一个 Button 组件,协商约定 c-xxxCard 组件 Slotb-xxxButton 组件 Slot,这样在经过分解之后就可以区分出应该往哪个组件穿透 Slot 了。

该方法同样可以处理 Slots 夸多层级的传递的问题。

ProCard.vue 中取的所有 slots 并且整理好各个组件所需 slots


// 首先还是取到所有Slots的key
const slots = Object.keys(useSlots())

// 定义一个buttonSlots,用来组装需要用到的Button组件的slots
const buttonSlots = ref<string[]>([])

// 定义一个cardSlots,用来组装需要用到的Card组件的slots
const cardSlots = ref<string[]>([])

// 找出各自组件需要的slot组装好push进去就可以在template中穿透到指定组件了
for (let slotIndex = 0; slotIndex < slots.length; slotIndex++) {
	const slotKey = slots[slotIndex];
	if (slotKey.indexOf('c-') > -1) {
		cardSlots.value.push(slotKey.slice(2, slotKey.length))
		continue
	}
	if (slotKey.indexOf('b-') > -1) {
		buttonSlots.value.push(slotKey.slice(2, slotKey.length))
	}
}

接下来就可以在 template 中直接使用了

<template>
    <div class="procard-container">
        <PureCard @close="onEmitClose" :handleClose="handleClose">
            <!-- 使用组装好的cardSlots -->
            <template v-for="(slotKey, slotIndex) in cardSlots" :key="slotIndex" v-slot:[slotKey]>
                <slot :name="`c-${slotKey}`">{{ slotKey }}</slot>
            </template>
        </PureCard>
        <PureButton @click="onButtonClick" :handleClick="handleButtonClick">
            <!-- 使用组装好的buttonSlots -->
            <template v-for="(slotKey, slotIndex) in buttonSlots" :key="slotIndex" v-slot:[slotKey]>
                <slot :name="`b-${slotKey}`">{{ slotKey }}</slot>
            </template>
        </PureButton>
    </div>
</template>

引入一下 ProCard 组件来看一下效果吧

<template>
    <div>
        <ProCard title="123">
            <template #c-title>
                <span>CardSlot标题</span>
            </template>
            <template #c-default>
                <span>CardSlot内容</span>
            </template>
            <template #c-footer>
                <span>CardSlot底部</span>
            </template>
            <template #b-default> 按钮 </template>
        </ProCard>
    </div>
</template>

方法 2:render 渲染函数

例如:my-tree-table组件

<script>
import { createVNode } from 'vue';
import myTable from '@c/my-table';
/**
 * 插槽穿透问题的处理方法
 */
const comTable = {
	/**
	 * @param {*} props
	 * @param {*} context
	 * Attribute (非响应式对象,等同于 $attrs) console.log(context.attrs)
	 * 插槽 (非响应式对象,等同于 $slots) console.log(context.slots)
	 * 触发事件 (方法,等同于 $emit) console.log(context.emit)
	 * 暴露公共 property (函数) console.log(context.expose)
	 */
	setup(props, context) {
		const {
			parent: {
				attrs,
				parent: { slots }
			}
		} = getCurrentInstance();
		/**
		 * createVNode接收三个参数:type,props 和 children
		 * type 类型:String | Object | Function
		 * 详细:HTML 标签名、组件、异步组件或函数式组件。使用返回 null 的函数将渲染一个注释。此参数是必需的。
		 * #props 类型:Object
		 * 详细:一个对象,与我们将在模板中使用的 attribute、prop 和事件相对应。可选。
		 * #children 类型:String | Array | Object
		 * 详细:子代 VNode,使用 createVNode() 生成,或者使用字符串来获取“文本 VNode”,或带有插槽的对象。可选。插槽也包含其中
		 */
		return () =>
			createVNode(
				myTable,
				{
					...attrs,
					ref: 'myTable'
				},
				{
					...slots
				}
			);
	}
};
</script>