Skip to content
On this page

Vue3如何跨层级传递Slot

随着项目的增大,在开发过程中会遇到这样一个问题,如何跨组件传递插槽,因为在开发类似树组件的过程中,插槽需要通过外部传递到树的根节点,然后通过根节点依次传递到各个叶子节点。那么如何把根节点的Slot如传递给子组件呢?

我们在开发过程中,希望可以这样实现重新定义叶子节点的结构:

js
// 如何在dt-theme中传入一个模板,然后在dt-logo中接收到?
<dt-theme>
    <dt-header>
        <dt-logo>
            <slot #logo></slot>
        </dt-logo>
    </dt-header>
</dt-theme>

下面是我如何实现的,不是通过传递Slot,而是通过子节点主动去获取根节点的Slot对象,然后直接在UI中渲染出来。

首先获取根节点:

ts
// utils/get-parent-slots.ts

/**
 * @param 
 *      rootComponentName 根据父组件的name查找
 *      cls 根据父节点的class名称查找
 */
export function getParentSlots(rootComponentName: string = '', cls?: string) {
    // 获取当前组件实例
    const currentInstance = getCurrentInstance()

    // 获取指定的父组件实例
    const getRootComponent = (
        component: ComponentInternalInstance | null
    ): ComponentInternalInstance | undefined => {
        let clsList = component.vnode.el?.className?.split(' ') ?? []

        if (component && ((rootComponentName && component.type.name === rootComponentName) || (cls && clsList.includes(cls))) ) {
            return component
        }

        if (component && component.parent) {
            const parent = component.parent
            return getRootComponent(parent)
        }
    }

    const rootCom = getRootComponent(currentInstance)
    const slots = rootCom?.slots || {}

    return slots
}

通过递归我们可以获取到对应的父节点的所有slots,这时候我们需要一个组件来渲染暴露出来的Slot。

ts
// components/slot-container.vue

<script lang="tsx">
    export default defineComponent({
        name: 'slot-container',
        props: {
            template: {
                type: Function
            },
            data: {
                type: Object
            }
        },
        setup(props) {
            return () => {
                return h('div', [props.template(props.data)])
            }
        }
    })
</script>

现在该准备的都准备好了,可以在页面中实现了。

ts
<template>
    <div>
        <SlotContainer v-if="slots.logo" :template="slots.logo" :data="{
            testTxt: "我是传给父组件的数据"
        }"></SlotContainer>

        <template v-else>
            <span>如果没有插槽,那么就渲染我吧!</span>
        </template>
    </div>
</template>

<script lang="ts" setup>
    import { getParentSlots } from 'utils/get-parent-slots.ts'
    import SlotContainer from 'components/slot-container.vue'

    const slots = getParentSlots('dt-theme')
    /**
     * 也可以根据class查找
     *  const slots = getParentSlots(null, 'dt-theme')
     */ 
</script>

本文使用的是Vue3的示例,也可以使用Provide/Inject来将Slot主动传递给子节点。