Vue3的插槽透传看这篇就够了

3,479 阅读2分钟

插槽(slot)透传在写组件和二次封装时尤为有用,我们总希望保持组件内部的插槽在二次封装后仍然提供使用。一个经典的开发背景:二次封装UI组件库的<table>组件(如ElementUI,Arco-Design,Ant-Design,巴拉巴拉)封装后期望自定义TableColumn仍然能够在业务页面能够通过slot传入。

开始

下面将以此为例进行讲解如何实现:(在vue3+setup下)

所有内容均在vue.js官方文档由更详细的说明:slot-插槽

原理图

image.png

语法基础

透传插槽需要了解及使用如下基础语法

  • 1.定义插槽及透传数据 <slot name="插槽名" :attr1="parentData1" :attr2="parentData2"></slot>
  • 2.使用插槽 <template #插槽名></template>
  • 3.动态插槽 <template #[dynamicSlotName]> </template>
    • 注意:动态名称有个中括号
  • 4.获取插槽 useSlots 见官方文档 useSlots
    <script setup> 
    import { useSlots, useAttrs } from 'vue'
    const slots = useSlots() 
    </script>
    

Code it

下方将以 Arco-Design 的table进行二次封装为例。

并不会将全部code展示,只会展示关键code

一、arco的table组件

如果你使用的是Element-UI 其实差不多,参考即可

详见Arco关于table的自定义渲染

 <a-table :columns="columns" :data="data">
    <template #optional="{ record }">
      <a-button @click="$modal.info({ title:'Name', content:record.name })">view</a-button>
    </template>
  </a-table>
  
    const columns = [{
      title: 'Name',
      dataIndex: 'name',
    }, {
      title: 'Salary',
      dataIndex: 'salary',
    }, {
      title: 'Address',
      dataIndex: 'address',
    }, {
      title: 'Email',
      dataIndex: 'email',
    }, {
      title: 'Optional',
      slotName: 'optional'  // 看这个slotName 和 <template> 上的插槽名一致
    }];

二、二次封装custom-table组件

 // @/components/custom-table/index.vue
<template>
 <a-table
      :columns="props.columns"
      :data="dataList"
    >
        <!-- ⭐️关键在这里 ⭐️
        1.遍历插槽
        2.使用 #[]来动态渲染插槽
        3.使用slot再定义一个同名插槽 (从业务页面接受custom-table的插槽)
          -->
      <template v-for="slot in columnSlots" :key="slot.name" #[slot.name]="{ record }">
        <slot :name="slot.name" :record="record"></slot>
      </template>
    </a-table>
 </template>
 
<script setup lang="ts">

import { useSlots, computed } from 'vue';
const slots = useSlots();
// ⭐️下面的computed 按各自需求处理就好
const curSlotName = ['需要排除掉的slotName,即表示无需透传的slot'];
const columnSlots = computed(() => {
if (slots) {
  return Object.keys(slots)
    .filter((key) => !curSlotName.includes(key))
    .map((t) => {
      return { name: t, slot: slots[t] };
    });
}
return [];
});   
</template>

三、在业务页面使用

// 自己的业务页面
<template>
<custom-table ref="tableRef" :columns="columns" :fetch-api="list">
      <!-- ⭐️这里只是示意 : slotName="createTime" -->
      <template #createTime="{ record }">
        {{ dayjs(record.createTime).format('YYYY-MM-DD') }}
      </template>
    </custom-table>
    
</template>
<script setup lang="ts">
  import { list} from '@/api/article';
  import customTable from '@/components/table/index.vue';
  const columns = [
    {
      title: '标题',
      dataIndex: 'title',
      ellipsis: true,
      tooltip: true,
      width: 200,
    },
    {
      title: '更新时间',
      dataIndex: 'createTime',
      slotName: 'createTime', # ⭐️定义slotName
      ellipsis: true,
      tooltip: true,
    }
  ];
</script>

完结,撒花 ❀❀❀❀❀❀