在编写vue通用组件时常常会遇到子组件的插槽需从父组件传递过去,并还能够给插槽绑定数据,如ant-design-vue中的下拉菜单多选项时能够通过插槽自定义tag的内容

先展现下成果:
组件默认成果:

应用了tag插槽的成果:

1、思路

外围思路是:将父组件中的插槽以参数的模式传递给子孙组件,而不是应用插槽传递,如<template #xxx><slot name="xxx"></slot></template>

这里有两种形式能够将父组件的插槽以参数模式传递给子孙组件:

  1. provide+inject模式
  2. 子组件定义props,父组件将slots传递给子组件

这两种形式各有千秋,看本人喜爱了

2、编码:应用 provide+inject 形式实现

父组件

<template>  <div class="input-tags">    <input type="text" class="input-tags-inputer">    <div class="input-tags-list">      <MyTag        v-for="tag in tags"        :key="tag.value"        :tag-data="tag"></MyTag>    </div>  </div></template><script>import {  provide} from 'vue';import MyTag from './MyTag.vue';export default {  name: 'InputTags',  props: {    tags: {      type: Array,      default () {        return [];      }    }  },  components: {    MyTag  },  setup (props, ctx) {    provide('inputTagsCtx', ctx);    return {};  }};</script><style lang="scss">.input-tags{  //display: inline-block;  position: relative;}.input-tags-inputer{  display: block;  width: 100%;  height: calc(1.5em + 0.75rem + 2px);  padding: 0.375rem 0.75rem;  font-size: 1rem;  font-weight: 400;  line-height: 1.5;  color: #495057;  background-color: #fff;  background-clip: padding-box;  border: 1px solid #ced4da;  border-radius: 0.25rem;  transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;  &:focus{    color: #495057;    background-color: #fff;    border-color: #80bdff;    outline: 0;    box-shadow: 0 0 0 0.2rem rgb(0 123 255 / 25%);  }}.input-tags-list{  position: absolute;  top: 1px;  left: 1px;  bottom: 1px;  right: 1px;  padding: 0.25rem 0 0 0.5rem;  .my-tag{    margin-right: 0.5rem;  }}</style>

子组件

<template>  <div class="my-tag">    <MyTagContent :tag-data="tagData">      {{ tagData.label }}    </MyTagContent>  </div></template><script>import { MyTagContent } from './MyTagContent';export default {  name: 'MyTag',  components: {    MyTagContent  },  props: {    tagData: {      type: [Object],      default () {        return {};      }    }  }};</script><style lang="scss">.my-tag{  display: inline-block;  vertical-align: middle;  height: 1.75rem;  line-height: calc(1.75rem - 2px);  max-width: 100%;  padding: 0 0.5rem;  border: 1px solid #d9ecff;  border-radius: 0.25rem;  background-color: #ecf5ff;  color: #007bff;  font-size: 0.75rem;  white-space: nowrap;  overflow: hidden;  text-overflow: ellipsis;  transition: width .3s, height .3s, font-size .3s, transform .3s;}</style>

tag内容组件(如果用jsx能够省略这个组件)

import {  inject} from 'vue';export function MyTagContent (props, ctx) {  let slot;  // 获取父级组件传递过去的上下文  let inputTagsCtx = inject('inputTagsCtx');  if (inputTagsCtx && inputTagsCtx.slots.tag) {    // 如果父组件传递了tag插槽则优先应用父组件传递的    slot = inputTagsCtx.slots.tag;  } else {    slot = ctx.slots.default;  }  return slot(props.tagData);};MyTagContent.props = ['tagData'];

组件调用

<template>  <div class="box">    <h3>默认的tag成果</h3>    <InputTags      :tags="[        { label: 'Html', value: 'Html' },        { label: 'Javascript', value: 'Javascript' },        { label: 'Css', value: 'Css' }      ]">    </InputTags>        <h3 style="margin-top: 1rem;">自定义tag</h3>    <InputTags      :tags="[        { label: 'Html', value: 'Html' },        { label: 'Javascript', value: 'Javascript' },        { label: 'Css', value: 'Css' }      ]">      <template #tag="tag">        标签名:<strong style="font-size: 1.2em;">{{ tag.label }}</strong>      </template>    </InputTags>  </div></template>

3、编码:应用 子组件定义props 形式实现

父组件

<template>  <div class="input-tags">    <input type="text" class="input-tags-inputer">    <div class="input-tags-list">      <MyTag        v-for="tag in tags"        :key="tag.value"        :tag-data="tag"        :parent-slots="$slots"></MyTag>    </div>  </div></template><script>import MyTag from './MyTag.vue';export default {  name: 'InputTags',  props: {    tags: {      type: Array,      default () {        return [];      }    }  },  components: {    MyTag  }};</script><style lang="scss">.input-tags{  //display: inline-block;  position: relative;}.input-tags-inputer{  display: block;  width: 100%;  height: calc(1.5em + 0.75rem + 2px);  padding: 0.375rem 0.75rem;  font-size: 1rem;  font-weight: 400;  line-height: 1.5;  color: #495057;  background-color: #fff;  background-clip: padding-box;  border: 1px solid #ced4da;  border-radius: 0.25rem;  transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;  &:focus{    color: #495057;    background-color: #fff;    border-color: #80bdff;    outline: 0;    box-shadow: 0 0 0 0.2rem rgb(0 123 255 / 25%);  }}.input-tags-list{  position: absolute;  top: 1px;  left: 1px;  bottom: 1px;  right: 1px;  padding: 0.25rem 0 0 0.5rem;  .my-tag{    margin-right: 0.5rem;  }}</style>

子组件

<template>  <div class="my-tag">    <MyTagContent :parent-slots="parentSlots" :tag-data="tagData">      {{ tagData.label }}    </MyTagContent>  </div></template><script>import { MyTagContent } from './MyTagContent';export default {  name: 'MyTag',  components: {    MyTagContent  },  props: {    tagData: {      type: [Object],      default () {        return {};      }    },    parentSlots: { // 父组件的插槽      type: [Object],      default () {        return {};      }    }  }};</script><style lang="scss">.my-tag{  display: inline-block;  vertical-align: middle;  height: 1.75rem;  line-height: calc(1.75rem - 2px);  max-width: 100%;  padding: 0 0.5rem;  border: 1px solid #d9ecff;  border-radius: 0.25rem;  background-color: #ecf5ff;  color: #007bff;  font-size: 0.75rem;  white-space: nowrap;  overflow: hidden;  text-overflow: ellipsis;  transition: width .3s, height .3s, font-size .3s, transform .3s;}</style>

tag内容组件(如果用jsx能够省略这个组件)

export function MyTagContent (props, ctx) {  let slot;  if (props.parentSlots.tag) {    // 如果父组件传递了tag插槽则优先应用父组件传递的    slot = props.parentSlots.tag;  } else {    slot = ctx.slots.default;  }  return slot(props.tagData);};MyTagContent.props = ['tagData', 'parentSlots'];

组件调用

<template>  <div class="box">    <h3>默认的tag成果</h3>    <InputTags      :tags="[        { label: 'Html', value: 'Html' },        { label: 'Javascript', value: 'Javascript' },        { label: 'Css', value: 'Css' }      ]">    </InputTags>        <h3 style="margin-top: 1rem;">自定义tag</h3>    <InputTags      :tags="[        { label: 'Html', value: 'Html' },        { label: 'Javascript', value: 'Javascript' },        { label: 'Css', value: 'Css' }      ]">      <template #tag="tag">        标签名:<strong style="font-size: 1.2em;">{{ tag.label }}</strong>      </template>    </InputTags>  </div></template>