胖蔡叨叨叨
你听我说

Vue 插槽(slot)的类型与使用

Vue slot是组件内容分发的出口,其设计规范源于:Web Components规范草案。一般的,基于Slot使用场景我们可以将其分为三大类,其中各个类型之间又存在着依次聚合的关系。

默认插槽

常见适用于静态、或者无强联系、分类布局的组件中。如网站底部的footer组件,我们会有些基础的元素,如copyright、开发公司、地址等,但还存在一些不确定是否公用的东西,如时间、统计等,这时,我们就可以通过slot开发一个分发的出口,让具体场景使用的开发人员自己决定是否使用扩展分发已经如何扩展的问题。默认插槽的使用方式如下:

// 子组件 Aritcile.vue

<template>
<div>
 <p> 这时子组件的标题 </p>
 <slot> <!--这是子组件的段落 --></slot>
</div>
</template>

<script>
.......
</script>

// 父组件 detail.vue

<template>
  <p> 父组件的标题 </p>
  <article>
     <p> 这里是需要分发的子组件的段落内容  </p>  
  </article>
</template>

<script>
import Aritcile from './Aritcile';

export default {
    components:{
         Aritcile,
    }
}

</script>

如此显示内容为:

父组件的标题
这时子组件的标题
这里是需要分发的子组件的段落内容

具名插槽

默认插槽在常见的常见基本使用,但存在需要多组件的多个部分进行自定义分发时,默认插槽的方式就不太适用了。因为无法指定位置不确定需要将内容插入到那个slot下。这时,我们就需要通过给slot一个名字来实现指定位置的插入。常见的场景如页面布局,我们需要将一个页面分为三个部分:头部、底部、内容展示区域,这时我们就需要给对应的slot进行命名。使用如下:


<!-- 子组件 page.vue  -->

<template>
  <div>
     <slot name="header"> </slot>
     <slot> </slot>
     <slot name="footer"> </slot>
  </div>


</template>
<script>
export default {

}
</script>

<!-- 父组件 dashborad.vue  -->

<template>
  <div>
   当前页面的显示内容

   <page>
      <div slot="footer"> 底部内容 </div>
      <div slot="header"> 頭部内容 </div>
      <div>打印正文内容 </div>
   </page>

  </div>
</template>
<script>

import Page from './page';
export default {
    components:{
        page
    }

}
</script>

显示内容如下:

当前页面的显示内容
頭部内容
打印正文内容
底部内容

作用域插槽

基于上述两种插槽的基础上,当我们需要在分发内容中获取到分发的参数内容,如对于一个列表而言,每个item在我们每次调用的时候都有可能不同,我们希望在列表组件中处理些通用逻辑,但具体item的样式我们需要足够的可扩展性,这是,我们就需要通过分发slot出口及其item的数据来实现插槽的功能,这就是作用域插槽。下面,通过一个列表的示例来了解。

<!-- 子组件 list.vue-->

<template>
  <div
    v-loading="loading"
  >
    <div v-if="dataSource && dataSource.length" class="content">
      <div class="content-list">
        <!--列表显示内容 -->
        <template v-for="item in dataSource">
          <slot name="listItem" v-bind="item" />
        </template>
      </div>

      <div class="content-pagination">
        <pagination
          :total="pagination.total"
          :page="pagination.current"
          :limit="pagination.pageSize"
          :layout="pagination.layout"
          :hideOnSinglePage="pagination.hideOnSinglePage"
          @paginationChange="handlePaginationChange"
        />

        <el-button
          v-if="btnName"
          type="primary"
          class="btn"
          :loading="btnLoading"
          @click.native="btnClick"
          >{{ btnName }}</el-button
        >
      </div>
    </div>
    <!-- 未读 -->
    <div v-else class="empty-box">
      <img
        src="@/assets/local_images/table_empty.png"
        class="empty-image"
        alt="logo"
      />
      <span>暂无相关数据</span>
    </div>
  </div>
</template>

<script>
import Pagination from "@/components/Pagination";

export default {
  name: "NotificationList",
  components: {
    Pagination,
  },

  data() {
    return {
      info: {}, // 数据源
      dataSource: [], // 数组数据源
      loading: false, // 加载中
      btnLoading: false,
      pagination: {
        /* 分页参数 */
        current: 1,
        pageSize: 10,
        pageSizeOptions: ["10", "20", "30", "50", "100"],
        layout: "total, prev, pager, next, sizes, jumper",
        total: 0,
      },
    };
  },
  methods: {
    setDateSource(data) {
      if (!data || !data.records || data.records.length === 0) {
        this.info = data || {}; // 设置数据源
        this.dataSource = []; // 数据源为空
        if (Number(this.pagination.current) === 1) {
          this.pagination.total = 0;
        } else {
          const currentPage = Number(this.pagination.current) - 1;
          const current = currentPage > 0 ? currentPage : 1;
          this.pagination.current = current;
          this.$emit("loadData", { current });
          //  这里需要重新加载数据
        }
      } else {
        console.log("get data:", data);
        // 数据不为空
        this.pagination.total = data.total;
        // 设置数据源
        this.dataSource = data.records.map((item) => {
          return {
            title: item.content,
            id: item.id,
            messageType: Number(item.messageType),
            date: item.sendTime,
            status: Number(item.status),
            domain: JSON.parse(item.messages),
          };
        });

        console.log("get pagination:", this.pagination);
      }
    },
    setLoading(flag) {
      this.loading = flag;
    },
    // 分页控制器变化
    handlePaginationChange(pageData) {
      this.pagination.current = pageData.page;
      this.pagination.pageSize = pageData.limit;
      this.$emit("loadData", { current: pageData.page, size: pageData.limit });
    },
    btnClick() {
      this.$emit("onSubmit"); // 按钮点击回调
    },
    setBtnLoading(flag) {
      // 设置btn的加载装填
      this.btnLoading = flag;
    },
  },
};
</script>




<! --父组件调用 -->

<template>
  <div class="container">

   <list>
       <template v-slot:listItem="record">
         <div>
            <div>
                <span class="time item-label">{{ record.time }}</span>
                <div class="title">{{ record.title }}</div>
            </div>

           <div class="second-line">
               <span class="date item-label">{{ record.date }}</span>
              <div class="domain">
                    {{ record.domainType}}
               <span class="domain-name">{{ record.domainName }}</span>
              </div>
            </div>

            <div v-if="record.approvalOpinion" class="third-line">
                审核意见:{{ record.approvalOpinion }}
            </div>
         </div>     
        </template>
   </list>
  </div>

</template>

<script>
import List from './list';
export default {
    components:{
        List
    }
}

</script>

如上,通过在组件list中 “ <slot name=“listItem” v-bind=“item” />“绑定数据,然后在父组件中使用 “ <template v-slot:listItem=“record”>“来获取数据。

赞(0) 打赏
转载请附上原文出处链接:胖蔡叨叨叨 » Vue 插槽(slot)的类型与使用
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏