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

先展示下效果:
组件默认效果:
组件默认效果

使用了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>

heath_learning
1.4k 声望31 粉丝