美文网首页前端
13.插槽的使用

13.插槽的使用

作者: 静昕妈妈芦培培 | 来源:发表于2021-08-20 08:38 被阅读0次

认识插槽Slot

在开发中,我们会经常封装一个个可复用的组件:

  • 我们会通过props传递给组件一些数据,让组件来进行展示;
  • 但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制为固定的divspan等等这些元素;
    • 比如某种情况下我们使用组件,希望组件显示的是一个按钮,某种情况下我们使用组件希望显示的是一张图片;
  • 我们应该让使用者可以决定某一块区域到底存放什么内容和元素;
举个栗子:假如我们定制一个通用的导航组件 - NavBar
  • 这个组件分成三块区域:左边-中间-右边,每块区域的内容是不固定;
  • 左边区域可能显示一个菜单图标,也可能显示一个返回按钮,可能什么都不显示;
  • 中间区域可能显示一个搜索框,也可能是一个列表,也可能是一个标题,等等;
  • 右边可能是一个文字,也可能是一个图标,也可能什么都不显示;


    image.png
这个时候我们就可以来定义插槽slot:
  • 插槽的使用过程其实是抽取共性、预留不同
  • 我们会将共同的元素内容依然在组件内进行封装;
  • 同时会将不同的元素使用slot作为占位,让外部决定到底显示什么样的元素;
如何使用slot呢?
  • Vue中将 <slot> 元素作为承载分发内容的出口;
  • 在封装组件中,使用特殊的元素<slot>就可以为封装组件开启一个插槽
  • 该插槽插入什么内容取决于父组件如何使用;

插槽的基本使用

插槽分为匿名插槽(默认插槽),具名插槽, 作用域插槽

匿名插槽的使用

插槽的基本使用

1. 在组件SlotCpn.vue中预留匿名插槽
<template>
  <div>
    <div>组件开始</div>
    <slot></slot>
    <div>组件结束</div>
  </div>
</template>
2. 在组件App.vue中使用组件SlotCpn.vue,并在组件开始标签<slot-cpn>和结束标签</slot-cpn>插入的所有内容,都会被插入到组件的所有的匿名插槽一份
<template>
  <div>
    <!-- 不插入任何内容 -->
    <slot-cpn></slot-cpn>
    <slot-cpn>大风预警</slot-cpn>
    <slot-cpn>
      <button>添加</button>
    </slot-cpn>

    <!-- 使用组件时,不管在组件开始标签和结束标签传几项,都会全部传给默认插槽 -->
    <slot-cpn>
      <input type="text" />
      <button>提交</button>
    </slot-cpn>
    <slot-cpn>
      <i>why</i>
      <span>水果</span>
      <button>按钮</button>
    </slot-cpn>
  </div>
</template>

<script>
import SlotCpn from "./SlotCpn.vue";
export default {
  components: {
    SlotCpn,
  },
};
</script>

执行npm run serve,并在浏览器预览,可以看到,使用组件时在组件开始标签结束标签插入的所有内容都被插入到匿名插槽所在位置了

image.png
3. 如果在组件SlotCpn.vue中预留了多个匿名插槽
<template>
  <div>
    <div>组件开始</div>
    <slot></slot>
    <div>---分割线---</div>
    <slot></slot>
    <div>组件结束</div>
  </div>
</template>

执行npm run serve,并在浏览器预览,可以看到,在组件SlotCpn.vue中的每个匿名插槽的位置都被插入了使用组件时在组件开始标签结束标签插入的所有内容

image.png
4. 如果希望在使用组件SlotCpn.vue时,在组件开始标签结束标签中不插入内容时,显示默认内容,我们可以给插槽设置默认值
<template>
  <div>
    <div>组件开始</div>
    <slot>
      <h1>我是插槽1的默认值</h1>
    </slot>
    <div>---分割线---</div>
    <slot>
      <h1>我是插槽2的默认值</h1>
      <button>提交</button>
    </slot>
    <div>组件结束</div>
  </div>
</template>

执行npm run serve,并在浏览器预览,可以看到,使用组件时在组件开始标签结束标签不插入内容,在匿名插槽的位置会显示设置的默认值;
使用组件时在组件开始标签结束标签插入了内容,在组件SlotCpn.vue中的每个匿名插槽的位置都被插入了使用组件时在组件开始标签结束标签插入的所有内容

image.png

具名插槽的使用

如果希望把插入在组件开始标签结束标签中的内容的不同部分插入到组件的指定插槽位置,可以使用具名插槽

  • 具名插槽顾名思义就是给插槽起一个名字,<slot> 元素有一个特殊的 attribute:name,可以通过给name赋值,给插槽起名
  • 一个不带 name 的slot,会带有隐含的名字 default,即name='default',匿名插槽其实就是名字为default的插槽
  • 在使用组件时,可以在组件开始标签结束标签中间使用template标签,给template标签通过绑定v-slot:插槽名来指定template标签之间的内容插入到哪个具名插槽的位置

组件NavBar.vue中预留了三个具名插槽,名字分别为'left', 'right', 'center'

<template>
  <div class="tabs">
    <div class="left">
      <slot name="left">标题</slot>
    </div>
    <div class="center">
      <slot name="center">
        <input />
      </slot>
    </div>
    <div class="right">
      <slot name="right">
        <span>...</span>
      </slot>
    </div>
  </div>
</template>

<script>
  export default {
  }
</script>

<style scoped>
  .tabs {
    display: flex;
    align-items: center;
  }
  .center {
    flex: 1;
  }
</style>

组件App.vue

<template>
  <div>
    <nav-bar></nav-bar>
    <!-- 具名插槽的v-slot:插槽名字  只能放在template上 -->
    <nav-bar>
      <!-- 指定其中的内容插入到名字为left的插槽所在的位置 -->
      <template v-slot:left>
        <span >中誉</span>
      </template>
      <!-- 指定其中的内容插入到名字为right的插槽所在的位置 -->
      <template v-slot:right>
        <button>添加</button>
      </template>
      <!-- 指定其中的内容插入到名字为center的插槽所在的位置 -->
      <template #center>
        <div >详情1</div>
      </template>
    </nav-bar>
  </div>
</template>

<script>
  import NavBar from './NavBar.vue'
  export default {
    components: {
      NavBar
    }
  }
</script>

执行npm run serve,并在浏览器预览,可以看到,插入在组件开始标签结束标签中的内容的不同部分被插入到组件的指定插槽位置啦

image.png

具名插槽使用的时候缩写

v-slot: 替换为字符 #

组件App.vue

<template>
  <div>
    <nav-bar>
      <template #left>
        <span >中誉</span>
      </template>
      <template #right>
        <button>添加</button>
      </template>
      <template #center>
        <div >详情1</div>
      </template>
    </nav-bar>
  </div>
</template>

<script>
  import NavBar from './NavBar.vue'
  export default {
    components: {
      NavBar
    }
  }
</script>

动态插槽的使用

组件NavBar.vue中一个具名插槽的名字,是由组件被使用时,动态绑定属性name的值决定的

<template>
  <div class="tabs">
    <div class="left">
      <slot name="left">标题</slot>
    </div>
    <div class="center">
      <slot name="center">
        <input />
      </slot>
    </div>
    <div class="right">
      <!-- 动态插槽名 -->
      <slot :name="name">
        <span>...</span>
      </slot>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    name: {
      type: String,
      default: "",
    },
  },
};
</script>

<style scoped>
.tabs {
  display: flex;
  align-items: center;
}
.center {
  flex: 1;
}
</style>

组件App.vue

<template>
  <div>
    <!-- 使用组件时给组件的属性name传值 -->
    <nav-bar :name="name">
      <template v-slot:left>
        <span >中誉</span>
      </template>
      <!-- 动态插槽名 -->
      <template v-slot:[name]>
        <button>添加</button>
      </template>
      <template #center>
        <div >详情1</div>
      </template>
    </nav-bar>
  </div>
</template>

<script>
  import NavBar from './NavBar.vue'
  export default {
    components: {
      NavBar
    },
    data() {
      return {
        name: 'right'
      }
    }
  }
</script>

作用域插槽的使用

渲染作用域

在Vue中有渲染作用域的概念:

  • 父级模板里的所有内容都是在父级作用域中编译的;
  • 子模板里的所有内容都是在子作用域中编译的;

如下例子:

  • 子组件ChildCpn.vue中的变量title,可以在子组件的模板中使用
  • 在父组件App.vue中导入了子组件ChildCpn.vue,注册为ChildCpn,并在使用子组件<child-cpn>时给子组件的默认插槽传入<span>{{title}}</span>
  • 虽然<span>{{title}}</span>被写在子组件开始标签<child-cpn>和结束标签</child-cpn>之间,但依然是在App.vue的模板中,那它的作用域依然是App.vue的作用域,App.vue的data中没有title变量,所以会报错
    image.png

认识作用域插槽

但是有时候我们希望在使用子组件时,在子组件的开始标签结束标签之间为子组件的插槽插入内容时,可以获取到子组件中的变量,Vue给我们提供了作用域插槽
下面看一个案例:

  • 1.在App.vue中定义好数组names,通过props传递给子组件MyCpns.vue组件中
  • 2.MyCpns.vue组件中遍历names数组,设置匿名插槽,并把遍历的数组的每一项的索引,作为插槽属性的值,绑定在插槽上,
  • 这些插槽上绑定的属性名和属性值,除了name属性名和值外都被存储在一个对象中,以被获取
  • 3.通过v-slot:插槽名="slotProps"的方式获取到存储着绑定在对应插槽上的属性名和属性值(name属性名和值除外)的对象slotProps
  • 4.通过对对象slotProps成员访问的方式,获取到对应的值,以在父组件App.vue中使用

MyCpns.vue组件

<template>
  <div>
    <!--遍历数组names-->
    <div v-for="(item, index) in names" :key="item">
      <!-- 把数组中当前遍历的元素和索引以插槽属性值的方式绑定到插槽上 -->
      <slot :item="item" :index="index"></slot>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    names: {
      type: Array,
      default: () => [],
    },
  },
};
</script>

App.vue组件

<template>
  <div>
    <my-cpn :names="names">
      <template v-slot:default="slotProps">
        <span>{{ slotProps.index }} - {{ slotProps.item }}</span>
      </template>
    </my-cpn>
  </div>
</template>

<script>
import MyCpn from "./MyCpn.vue";
export default {
  components: {
    MyCpn,
  },
  data() {
    return {
      names: ["john", "kobe", "why"],
    };
  },
};
</script>

<style scoped></style>

如果我们的插槽是默认插槽default,那么在使用的时候 v-slot:default="slotProps"可以简写为v-slot="slotProps"

App.vue组件

<template>
  <div>
    <my-cpn :names="names">
      <template v-slot="slotProps">
        <span>{{ slotProps.index }} - {{ slotProps.item }}</span>
      </template>
    </my-cpn>
  </div>
</template>

<script>
import MyCpn from "./MyCpn.vue";
export default {
  components: {
    MyCpn,
  },
  data() {
    return {
      names: ["john", "kobe", "why"],
    };
  },
};
</script>

独占默认插槽

如果我们在使用组件时,只给组件的默认插槽插入内容,不管组件在定义时是否设置了具名插槽,组件的标签可以被当做插槽的模板来使用,这样,我们就可以将 v-slot 直接用在组件标签上,这种写法叫做:独占默认插槽
App.vue组件

<template>
  <div>
    <!-- 省略了template标签 组件的开始标签和结束标签中的内容会被插入到组件的默认插槽位置 -->
    <my-cpn :names="names" v-slot="slotProps">
       <span>{{ slotProps.index }} - {{ slotProps.item }}</span>
    </my-cpn>
  </div>
</template>

<script>
import MyCpn from "./MyCpn.vue";
export default {
  components: {
    MyCpn,
  },
  data() {
    return {
      names: ["john", "kobe", "why"],
    };
  },
};
</script>

同事给匿名插槽和具名插槽都插入内容

如果我们在使用组件时,给组件的默认插槽和具名插槽都插入内容,则不可省略template标签
App.vue组件

<template>
  <div>
    <my-cpn :names="names">
      <template v-slot="slotProps">
        <span>{{ slotProps.index }} - {{ slotProps.item }}</span>
      </template>
      <template v-slot:bottom="slotProps">
        <span>今天天气不错</span>
      </template>
    </my-cpn>
  </div>
</template>

<script>
import MyCpn from "./MyCpn.vue";
export default {
  components: {
    MyCpn,
  },
  data() {
    return {
      names: ["john", "kobe", "why"],
    };
  },
};
</script>

MyCpns.vue组件

<template>
  <div>
    <div v-for="(item, index) in names" :key="item">
      <!--匿名插槽-->
      <slot :item="item" :index="index"></slot>
      <!--具名插槽-->
      <slot name="why">coderwhy</slot>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    names: {
      type: Array,
      default: () => [],
    },
  },
};
</script>

跨组件插槽的使用

image.png

要实现这个目标,需要做:

1.给hy-table.vue设置若干个具名插槽
2.page-content使用子组件hy-table.vue时,给对应具名插槽所在位置插入内容时,插入的内容为设置同名具名插槽,供使用page-content.vue的父组件使用
3.goods.vue使用子组件page-content.vue,给对应插槽位置插入想要插入的内容就行了,插入的内容最终会被传入hy-table.vue中设置的同名具名插槽所在位置

hy-table.vue
我们使用element-plus封装了一个hy-table.vue组件,table中展示哪些列由propList决定,在table.vue中动态设置具名插槽

<template>
  <div class="hy-table">
    <el-table
      :data="listData"
      border
      style="width: 100%"
    >
      <template v-for="propItem in propList" :key="propItem.prop">
        <el-table-column v-bind="propItem" align="center" >
          <template #default="scope">
            <!-- 在默认插槽内容,动态注册具名插槽 -->
            <slot :name="propItem.slotName" :row="scope.row">{{scope.row[propItem.prop]}}</slot>
          </template>
        </el-table-column>
      </template>
    </el-table>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  name: "hy-table",
  props: {
    listData: {
      type: Array,
      required: true,
    },
    propList: {
      type: Array,
      required: true,
    },
  },
  setup(props, { emit }) {
    return { };
  },
});
</script>

<style scoped lang="less">
.hy-table {
}
</style>

page-content.vue
在此组件中使用子组件hy-table.vue,给hy-table.vue一部分具名插槽插入固定形式的内容,另一部分具名插槽内部重新注册同名具名插槽,供使用page-content.vue的父组件自动义插入内容

<template>
  <div class="page-content">
    <hy-table
      :list-data="list"
      :prop-list="propList"
    >
      <!-- table 列公共插槽:status/createAt/updateAt/operate -->
      <template #status="scope">
        <el-button
          size="mini"
          :type="scope.row.enable ? 'primary' : 'danger'"
          >{{ scope.row.enable ? "启用" : "禁用" }}</el-button
        >
      </template>
      <template #createAt="scope">
        <span>{{ $filter.formatUtcTime(scope.row.createAt) }}</span>
      </template>
      <template #updateAt="scope">
        <span>{{ $filter.formatUtcTime(scope.row.updateAt) }}</span>
      </template>
      <template #operate">
        <div class="operate">
          <el-button
            type="text"
            icon="el-icon-edit"
            >编辑</el-button
          >
          <el-button
            type="text"
            icon="el-icon-delete"
            >删除</el-button
          >
        </div>
      </template>
      <!-- 列非公共插槽:非公共插槽中注册具名插槽,供页面使用 -->
      <template
        v-for="item in otherPropSlots"
        :key="item.prop"
        #[item.slotName]="scope"
      >
        <template v-if="item.slotName">
          <slot :name="item.slotName" :row="scope.row"></slot>
        </template>
      </template>
    </hy-table>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, ref, watch } from "vue";
import HyTable from "@/base-ui/table";
import { useStore } from "@/store";
export default defineComponent({
  name: "page-content",
  components: {
    HyTable,
  },
  props: {
    propList: {
      type: Array,
      required: true,
    },
    pageName: {
      type: String,
      required: true,
    },
  },
  setup(props, { emit }) {
    //1.获取列数据
    const store = useStore();
    const pageInfo = ref({
      pageSize: 10,
      pageNum: 1,
    });
    const getPageData = (queryInfo: any = {}) => {
      store.dispatch("system/getPageListAction", {
        pageName: props.pageName,
        queryInfo: {
          offset: pageInfo.value.pageSize * (pageInfo.value.pageNum - 1),
          size: pageInfo.value.pageSize,
          ...queryInfo,
        },
      });
    };
    getPageData();
    const list = computed(() =>
      store.getters["system/pageListData"](props.pageName),
    );

    //2.从内容配置的属性列表中过滤出有非公共插槽的属性配置数组
    const publicPropSlots = ["status", "createAt", "updateAt", "operate"];  
    const otherPropSlots = props.propList.filter((prop: any) => {
      return prop.slotName && !publicPropSlots.includes(prop.slotName);
    });

    return {
      list,
      otherPropSlots,
    };
  },
});
</script>

<style scoped lang="less">
.page-content {
  padding: 20px;
  border-top: 20px solid #f5f5f5;
}
</style>

goods.vue
在页面中使用page-content组件,并给具名插槽插入内容,最终内容会被插入在hy-table.vue中同名具名插槽所在的位置

<template>
  <div class="goods">
    <page-content
      :prop-list="propList"
      pageName="goods"
    >
      <template #img="scope">
        <el-image
          style="width: 60px; height: 60px"
          :src="scope.row.imgUrl"
          :preview-src-list="[scope.row.imgUrl]"
        >
        </el-image>
      </template>
    </page-content>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import PageContent from "@/components/page-content";
export default defineComponent({
  name: "goods",
  components: {
    PageContent,
  },
  setup() {
    const propList = [
      { prop: "name", label: "商品名", minWidth: "180" },
      { prop: "oldPrice", label: "原价", minWidth: "180" },
      { prop: "newPrice", label: "现价", minWidth: "180" },
      { prop: "imgUrl", label: "图片", minWidth: "180", slotName: "img" },
      { prop: "status", label: "状态", minWidth: "180", slotName: "status" },
      {
        prop: "createAt",
        label: "创建时间",
        minWidth: "180",
        slotName: "createAt",
      },
      {
        prop: "updateAt",
        label: "更新时间",
        minWidth: "180",
        slotName: "updateAt",
      },
      {
        label: "操作",
        minWidth: "180",
        slotName: "operate",
      },
    ],
 
    return {
      propList,
    };
  },
});
</script>

<style scoped lang="less"></style>

此文档主要内容来源于王红元老师的vue3+ts视频教程

相关文章

  • 13.插槽的使用

    认识插槽Slot 在开发中,我们会经常封装一个个可复用的组件: 我们会通过props传递给组件一些数据,让组件来进...

  • vue 插槽 slot

    插槽使用 普通插槽 具名插槽 使用具名插槽 从插槽里面传值出来如何接收? 如: 如何判断某个插槽是否被使用 组件内...

  • 【Vue8】插槽

    插槽很重要!插件和模块中会经常使用插槽。 插槽的使用场景 即什么时候使用插槽?比如说这样: 我是插槽slot 父组...

  • vue 插槽slot

    插槽的定义: 插槽的使用:

  • Vue 插槽

    1.插槽的简单使用 在组件中声明一个插槽 使用组件时 可以替换插槽 如果不替换插槽就使用默认值 2.具名...

  • vue插槽

    vue插槽slot的理解与使用 vue slot插槽的使用介绍及总结

  • 【前端Vue】05 - [插槽 ,前端模块化开发,webpack

    1. 插槽 1.1 插槽的基本使用 组件的插槽: 组件的插槽也是为了让我们封装的组件更加具有扩展性。 让使用者可以...

  • Vue学习笔记[12]-slot插槽和编译作用域

    为了使组件具有更多的扩展性,需要使用插槽,使用插槽的组件应为可复用的组件,若为不复用组件,则不应该使用插槽. 使用...

  • vue插槽

    vue插槽使用 1.基本插槽实现父级设置 子级设置 2、使用name设置插槽显示隐藏父组件使用v-slot绑定 子...

  • Vue-使用插槽

    二。具名卡槽 作用域插槽:插槽循环时使用

网友评论

    本文标题:13.插槽的使用

    本文链接:https://www.haomeiwen.com/subject/sfugvltx.html