封装Vue3组件小技巧:插槽转发

Dec 12, 2023

背景

在 Vue 中,我们可以使用插槽来给子组件传递内容。但是,如果我们需要给多个组件传递插槽,并且这些组件之间存在重名的插槽,该如何解决呢?

本文将介绍一种使用插槽别名和插槽转发的方案来解决这个问题。

示例代码:

<!-- CompSlot.vue -->
<script setup lang="ts">
</script>
<template>
  <div>
    <a-input />
    <a-input-number />
  </div>
</template>
<!-- 使用 CompSlot 组件 -->
<template>
    <CompSlot></CompSlot>
</template>
<script setup lang="ts">
import CompSlot from './CompSlot.vue'
</script>

怎么传props

如果需要传 props 进去的话,我可以定义两个参数:

  • inputProps:接收 Input 的参数
  • numberProps:接收 InputNumber 的参数
<!-- CompSlot.vue -->
<script setup lang="ts">
import { PropType } from 'vue'
import { InputNumberProps, InputProps } from 'ant-design-vue'
defineProps({
    inputProps: {
        type: Object as PropType<InputProps>,
        default: () => ({}),
    },
    numberProps: {
        type: Object as PropType<InputNumberProps>,
        default: () => ({}),
    },
})
</script>
<template>
    <div>
        <a-input v-bind="{ ...inputProps }"></a-input>
        <a-input-number v-bind="{ ...numberProps }"></a-input-number>
    </div>
</template>

这样就能分别给这两个组件传参数了,但这不是今天文章的重点

怎么传插槽

上面是给这两个组件传 props,那如果要给这两个组件传插槽怎么做呢?我们首先来要思考一个问题,这两个组件具备一些重名的插槽名称,就比如 prefix 这个插槽

prefix
prefix

那我们要怎么去做,才能更好地给这两个组件传插槽呢?先来讨论外部怎么传,想要保证这两个组件的插槽传递不发生冲突,咱们可以使用插槽别名,这也是很多组件库现在采用的方案,代码如下

<!-- 使用 CompSlot 组件 -->
<script setup lang="ts">
import CompSlot from '@/components/CompSlot.vue'
import { EditOutlined, FormOutlined } from '@ant-design/icons-vue'
const inputSlots = {
  // 插槽别名
  prefix: 'input-prefix',
}
const numberSlots = {
  // 插槽别名
  prefix: 'number-prefix',
}
</script>
<template>
  <div class="card">
    <CompSlot :input-slots="inputSlots" :number-slots="numberSlots">
      <!-- 使用插槽 -->
      <template #input-prefix> <EditOutlined /> </template>
      <template #number-prefix> <FormOutlined /> </template>
    </Comp>
  </div>
</template>

那么组件内部应该怎么去接收这些插槽呢?这就可以使用插槽转发的方案,我们先看看 CompSlot 内部遍历 inputSlots 是什么

<!-- CompSlot.vue -->
<script setup lang="ts">
defineProps({
  inputSlots: {
    type: Object,
    default: () => ({}),
  },
  numberSlots: {
    type: Object,
    default: () => ({}),
  },
})
</script>
<template>
  <div>
    <template v-for="(_, name) in inputSlots" :key="name"> {{ name }} --> {{ _ }</template>
  </div>
</template>

输出 prefix --> input-prefix

可以看到当 v-for 遍历一个对象的时候,键值对是倒过来的,跟数组不一样

我们可以利用这个特性,做插槽转发

最终 CompSlot.vue 的代码

<!-- CompSlot.vue -->
<script setup lang="ts">
import { InputNumberProps, InputProps } from 'ant-design-vue'
import { PropType } from 'vue'
 
defineProps({
  inputProps: {
    type: Object as PropType<InputProps>,
    default: () => ({}),
  },
  numberProps: {
    type: Object as PropType<InputNumberProps>,
    default: () => ({}),
  },
  inputSlots: {
    type: Object,
    default: () => ({}),
  },
  numberSlots: {
    type: Object,
    default: () => ({}),
  },
})
</script>
<template>
  <div>
    <a-input v-bind="{ ...inputProps }">
      <template v-for="(_, name) in inputSlots" :key="name" #[name]>
        <slot :name="inputSlots[name]" />
      </template>
    </a-input>
    <a-input-number v-bind="{ ...numberProps }">
      <template v-for="(_, name) in numberSlots" :key="name" #[name]>
        <slot :name="numberSlots[name]" />
      </template>
    </a-input-number>
  </div>
</template>
<style scoped></style>

达到了我们想要的效果~

效果
效果

在这种方案中,我们使用插槽别名来解决插槽名称冲突,并使用插槽转发来将插槽内容传递给子组件。该方案具有以下优点:

  • 代码简洁,易于理解
  • 可扩展性强

总结

在 Vue 中,使用插槽别名和插槽转发可以有效地解决给多个组件传递插槽的问题。该方案具有代码简洁、易于理解、可扩展性强等优点。

Back