作用域插槽的深入理解(用作用域插槽封装类似antdv的table)

1. slot是什么

在说作用域插槽前,需要先来了解一下插槽(slot)

在HTML中,slot标签作为 Web Components 技术套件的一部分,是 Web 组件内的一个占位符,该占位符可以在后期使用自己的内容进行填充

举个例子:

一台相机,由单机身和镜头组成,而单机身的镜头安装口就是插槽,同一台相机可以更换不同的镜头,这样相机拍出来的效果也就会不一样。

// 父组件
 <div>
     <children>
        <div class="lens">我是插槽</div>
     </children>
</div>


// 子组件
<div>
   <template>
      <p>我是插槽上面的内容</p>
      <slot></slot>
   </template>
</div>


// 页面展示的效果
// 我是插槽上面的内容
// 我是插槽

在上面的代码中,Children 组件就是单机身, 类名为lens的div就是镜头,在 Children 组件里面的slot就是给单机身留下的给镜头的镜头口,也就是插槽,这样 lens 就会自动填充在 slot 的位置,我讲明白了吗?

1.1 插槽的分类

1.1.1 默认插槽

我上面那个就是默认插槽,slot啥属性也没有,<div class="lens">我是插槽</div>直接填充slot、

1.1.2 具名插槽

顾名思义就是有名字的插槽,当组件插槽里面的内容较为复杂时,可以用具名插槽让对应的内容到指定的位置去

// 父组件
<template>
    <div>
        <children>
            <div>我是插槽</div>
            <div slot:name="content">我是有名字的插槽的内容</div>
        </children>
    </div>
</template>


// 子组件
<template>
    <div>
        <template>
            <div>我是插槽上面的内容</div>
            <slot></slot>
            <slot name="content"></slot>
        </template>
    </div>
</template>


// 页面展示内容
// 我是插槽上面的内容
// 我是插槽
// 我是有名字的插槽的内容

1.1.3 作用域插槽

好了,这个就是今天的重点了,子组件可以在作用域上绑定属性将子组件的信息传递给父组件使用,这些属性就会被挂在父组件

// 子组件
<template>
    <div>
        <template>
            <div>我是插槽上面的内容</div>
            <slot></slot>
            <slot name="content"></slot>

            <slot name="objSlot" :obj="obj"></slot>
        </template>
    </div>
</template>

<script>
export default {
    data(){
        return {
            obj: {
                name: 'Asuka',
                age: 21,
                position: '前端'
            }
        }
    }
}
</script>

// 父组件
<template>
    <div>
        <children>
            <div>我是插槽</div>
            <div slot:name="content">我是有名字的插槽的内容</div>
            <div slot="objSlot" slot-scope="{ obj }">{{ obj }}</div>
        </children>
    </div>
</template>


// 页面展示内容
// 我是插槽上面的内容
// 我是插槽
// 我是有名字的插槽的内容
// { "name": "Asuka", "age": 21, "position": "前端" }


到这里,突然想到,我可不可以给子组件传递props参数,然后当我需要一些自定义内容的时候再拿到props参数的值进行自定义呢?

那事不宜迟,说干就干,就用table:当某一些列需要进行一些特定的修改的时候,例如改成<a></a>,或者给一列作为按钮,点击按钮可以获得这一行的信息,进行一些增删查改的操作。

2. 作用域插槽封装Table

封装的思路就是,给table组件传递props参数

dataSource:数据源,也就是表格展示的每一行的信息

columns: 表头,这个就是字面意思

2.1 定义数据源

dataSource: [
                {
                    name: '流沙(Reimagined)',
                    singer: '陶喆',
                    img: 'https://pic.imgdb.cn/item/6500fdc0661c6c8e543d6ba4.jpg'
                },{
                    name: '我期待',
                    singer: '张雨生',
                    img: 'https://pic.imgdb.cn/item/656557a3c458853aef80e493.jpg'
                },{
                    name: '聖なる夜の贈り物 (圣夜的馈赠)',
                    singer: '秦基博',
                    img: 'https://pic.imgdb.cn/item/656557aec458853aef80fc36.jpg'
                }
            ],
            columns: [{
                title: '专辑封面',
                dataIndex: 'img',
                key: 'img',
                width: '200',
            },{
                title: '歌名',
                dataIndex: 'name',
                key: 'name',
                width: '200'
            },{
                title: '歌手',
                dataIndex: 'singer',
                key: 'singer',
                width: '200'
            },{
                title: '操作',
                dataIndex: 'options',
                key: 'options',
                width: '200',
                align: 'center'                
            }],

2.2 Table组件

根据前面说的作用域插槽对table进行封装

<template>
    <div class="bigbox">
        <table>
            <thead>
                <tr>
                    <th class="th" v-for="col in columns" :key="col.key">
                        <div class="th-item">
                            {{ col.title }}
                        </div>
                    </th>
                </tr>
            </thead>

            <tbody>
                <tr v-for="(item, index) in dataSource" :key="index">
                    <td class="td" v-for="col in columns" :key="col.key">
                        <slot :name="col.dataIndex" :record="item" :index="index" :text="item[col.dataIndex]"></slot>
                    </td>
                </tr>
            </tbody>

        </table>
    </div>
</template>

<script>
export default {
    props: {
        dataSource: {
            type: Array,
            required: true,
        },
        columns: {
            type: Array,
            required: true,
        }
    },
    data(){
        return {
        }
    }
}
</script>

<style lang="less" scoped>
.bigbox {
    margin: 0 auto;
}
table {
    border-collapse: collapse;
}
.th {
    color: #2c2c2c;
    font-weight: bold;
    font-size: 19px;
    text-align: left;
    background-color: #f2f2f2;
    padding: 10px 20px;

    .th-item{
        padding: 5px 20px;
        padding-left: 5px;
    }
}
.td{
    padding: 20px 25px;
    padding-left: 25px;
    font-size: 18px;
    font-weight: 400;
    text-align: left;
    border-top: 1px solid #e5e7eb;
    border-bottom: 1px solid #e5e7eb;
}
</style>

我们在父组件中传入props值

 <children :columns="columns" :dataSource="dataSource"></children>

这个时候打开网页你会发现除了表头啥也没有,别紧张,这个是正常的,不妨我们看看table组件的代码 

我们在table组件中,只有一个slot的插槽,而在使用table组件的时候我们什么内容也没有,结合前面的相机的例子,也就是虽然给单机身留下了安装镜头的位置,但是你根本就没有安装镜头,所以也就什么都没有

但是这样就不太符合实际情况了,所以此时我就需要给一个默认的镜头,即使我们不自己安装镜头,也有一个标准(默认)镜头在机身上面,也可以实现我们想要的效果

对应表格也就是说,我们只对某一些列要进行修改,不修改的就按照组件默认的样式展示就行

所以就要进行判断

<template>
    <div class="bigbox">
        <table>
            <thead>
                <tr>
                    <th class="th" v-for="col in columns" :key="col.key">
                        <div class="th-item">
                            {{ col.title }}
                        </div>
                    </th>
                </tr>
            </thead>

            <tbody>
                <tr v-for="(item, index) in dataSource" :key="index">
                    <td class="td" v-for="col in columns" :key="col.key">
                        <template v-if="handleCheckIsSlots(col.dataIndex)">
                            <slot :name="col.dataIndex" :record="item" :index="index" :text="item[col.dataIndex]"></slot>
                        </template>

                        <template v-else>
                            <p>{{ item[col.dataIndex] }}</p>
                        </template>
                    </td>
                </tr>
            </tbody>

        </table>
    </div>
</template>

<script>
export default {
    props: {
        dataSource: {
            type: Array,
            required: true,
        },
        columns: {
            type: Array,
            required: true,
        }
    },
    data(){
        return {
            scopedSlotsArr: [],
        }
    },
    mounted(){
        this.scopedSlotsArr = Object.keys(this.$scopedSlots);
    },
    methods:{ 
        handleCheckIsSlots(slot){
            return this.scopedSlotsArr.includes(slot);
        }
    },
}
</script>

这里解释一下 判断 和 handleCheckIsSlots 方法

判断,这里我在研究的时候发现了一个问题,我在网上查找资料,大家都说通过 this.$slots 里面是否有插槽的内容,我也这么做了,但是根本没有用,我也没有意识到是这个的问题,导致我走了很多弯路,后面我输出发现 this.$slots 根本就是个空对象,这自然不行啊,但是网上说的也不是没有道理的,后来我发现,如果是作用域插槽,在插槽内容上写了 slot-scope 那就是不行,如果没写,那么$slots就是正确的,但是我们也需要获得这一行的内容,那怎么办呢。

这是我就看到 this.$scopedSlots 这里面也有值啊

所以我在挂载的时候,把这个对象的每个键取出来作为判断的标准

handleCheckIsSlots 通过传入的slot来判断 $scopedSlots 是否包含,如果包含,就说明在父组件里有自定义,否就是没有

<children :columns="columns" :dataSource="dataSource">
      <template slot="name" slot-scope="{ record }">
          <p style="color: red;">{{ record.name }}</p>
      </template>
</children>

所以上面图片的 $scopedSlots 就只有name,而没有其他的表头 img、singer什么的

此时页面的效果就基本上完成了,接下来就是对列的自定义了

<template>
    <div>
        <children :columns="columns" :dataSource="dataSource">
            <template slot="name" slot-scope="{ record }">
                <p style="color: red;">{{ record.name }}</p>
            </template>

            <template slot="img" slot-scope="{ record }">
                <img class="img" :src="record.img" alt=""/>
            </template>

            <template slot="options" slot-scope="{ record, index }">
                <div class="btns">
                    <div @click="handleEdit(index)" class="edit">编辑</div>
                    <div @click="handleRemove(index)" class="delete">删除</div>
                </div>
            </template>
        </children>
    </div>
</template>


<script>
......省略的内容

methods: {
        handleEdit(index){
        },
        handleRemove(index){
            this.dataSource = this.dataSource.filter((item, index2) => index2 !== index);
        }
    }
</script>

 

除此以外,你还可以对表头的样式进行修改,比如它的宽度和对其方式,我已经在表头的属性里面写了 widthalign

<thead>
      <tr>
          <th :style="{ width: col.width + 'px' }" class="th" v-for="col in columns" :key="col.key">
                <div :style="{ textAlign: col.align }" class="th-item">
                     {{ col.title }}
                </div>
          </th>
      </tr>
</thead>

好了,到这里也就结束了,关于样式可以自己去做自定义的修改,当然这个组件还不能比肩Antdv、ElementUI这些组件库,这篇文章的目的只是为了让大家能够对作用域插槽有一个更深入的了解,也能帮助大家对那些组件库的 Table 组件的使用的原理有更明了的认识

好了,我是Asuka

这是我的个人网站:Nikaido Asuka

咱们,下次再见!

  • 15
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值