前端

13.插槽的使用

2021-08-20  本文已影响0人  静昕妈妈芦培培

认识插槽Slot

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

举个栗子:假如我们定制一个通用的导航组件 - NavBar
这个时候我们就可以来定义插槽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

具名插槽的使用

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

组件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中有渲染作用域的概念:

如下例子:

认识作用域插槽

但是有时候我们希望在使用子组件时,在子组件的开始标签结束标签之间为子组件的插槽插入内容时,可以获取到子组件中的变量,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视频教程

上一篇 下一篇

猜你喜欢

热点阅读