昨天初步体验了一把form表单的封装,觉得还是有些浅显且有些地方没有理清,所以今天来试一试B端项目中同样常用的一个组件---el-table的二次封装,今天的封装想要达到的效果:
1.和el-form组件一样通过传入对象的形式对table表格进行条件渲染
2.能够在实际调用场景下通过插槽来自定义标题,提交按钮等一些小东西
3.能够通过v-bind绑定属性实现开关组件,编辑/删除,多选框等操作按钮的显示
开局还是先准备一下前置知识:
1.父子组件之间的数据传递方式:通过ref 通过v-model 通过v-bind
2.v-for v-if条件渲染
3.插槽的基础使用
以上知识都是本次封装中需要用到的内容,不熟的话可以先去学习这些,否则我文笔逻辑有限可能阅读会有障碍.
------------------------------------------------------------分割线------------------------------------------------------------
表格基本内容展示:
开始封装的第一步肯定是要创建一个文件然后从element-plus中找到心仪的表格复制粘贴
这个看起来简单明了,结构肯定也很简单,就他了,我们来看一下他的结构:
el-table标签我就简单理解为装有表格的盒子,上面:data绑定的是表格中的数据,el-table-column标签则是决定了表格每一列的内容是什么,有几个el-table-column就有多少列,label则是每一列的标题,图中箭头和方框则是他们的对应关系.不难看出,tableData中的对象有几个属性,表格就有几列.因此我们可以根据他属性的数量v-for循环生成el-table-column.
到此我遇到了本次尝试中的第一个问题:如何动态的拿到数据中的属性且v-for绑定上去
解决方案如上图:首先定义一个空数组tableNameList,循环遍历tableData中的第一个对象中的属性(这里有些偷懒,默认的认为实际开发中后端传来的数据是规整的,每个对象中的属性是相同的)把他装入tableNameList中.
还有一个label属性没有确定,我把他也变成了一个配置文件也就是tableLabel,这样在上文中处理tableNameList时就可以把label也顺便确定
处理完的tableNameList也贴一下
然后就可以按照tableNameList进行v-for循环了
到这里我们表格的基本展示功能已经实现差不多了
一些其他东西:
在el-table中加入el-switch开关:
多了一个switch开关意味着多了一列,我们需要在tableData和tableLabel中添加响应的属性
按照上述逻辑,他会自己处理tableNameList并且往表格中新加一列
我们只需要在v-for中判断 让他循环到status时显示一下switch组件就好了
这里需要注意:el-switch中的v-model必须绑定且不能是同一个变量,否则会同开同关.红框中的v-model绑定的是本行中数据的status
看看效果:
在el-table中加入编辑/删除按钮:
由于操作栏不涉及数据,所以就不用动上面的数组对象了,直接el-table-column创建一列加插槽写两个按钮就可以了
通过传入isNeedDelete等几个变量的值来控制是否需要渲染这一列
加入多选框:
按照文档操作即可,选择的值改变时会触发示例上绑定的handleSelectiongChange函数,怎么操作这些数据就看具体的业务需求就好.
贴下代码:
调用层:在具体页面中对封装好的组件进行调用
<template>
<div class="father">
退款记录
<MyTable
v-model="multipleSelection"
:tableLabel="tableLabel"
:tableSetConfig="tableSetConfig"
:isMultiple="true"
:isNeedEdit="true"
:isNeedDelete="true"
>
<template #buttonsSecond>
<el-button type="primary" @click="dataSub">提交</el-button>
</template>
</MyTable>
</div>
</template>
<script lang="ts" setup>
import Vue, { ref } from 'vue'
import tableSetConfig from "@/components/tableSetConfig"
import MyTable from '@/components/MyTable.vue';
import tableLabel from '@/components/tableLabel';
const multipleSelection = []
const dataSub = ()=>{
console.log(multipleSelection.value[0]);
}
</script>
<style lang="less" scoped>
.father {
margin: 100px;
}
</style>
数据层:对业务层需要绑定的数据进行处理,比如按照后端给的数据动态生成el-table-column需要的name和label
<template>
<TableSet
v-model="multipleSelection"
:tableNameList="tableNameList"
v-bind="tableSetConfig"
:isMultiple="isMultiple"
:isNeedEdit="isNeedEdit"
:isNeedDelete="isNeedDelete"
@refChange="refChange"
>
<template #buttons>
<slot name="buttonsSecond"></slot>
</template>
</TableSet>
</template>
<script setup lang="ts">
import TableSet from './TableSet.vue';
//import tableSetConfig from "@/components/tableSetConfig"
//import tableLabel from './tableLabel';
import { ref, toRaw, watch, watchEffect } from 'vue';
let props = defineProps({
tableLabel:{
type:Object
},
tableSetConfig:{
type:Object
},
isMultiple:{
type:Boolean
},
isNeedEdit:{
type:Boolean
},
isNeedDelete:{
type:Boolean
},
modelValue: {
type: Array
}
})
const multipleSelection = ref([])
let refChange = ()=>{
props.modelValue.value=toRaw(multipleSelection.value.value)
}
let tableNameList = []
for(let key in props.tableSetConfig.tableData[0]){
console.log(key);
tableNameList.push({
name:key,
label:props.tableLabel[key]
})
console.log(tableNameList);
}
</script>
业务层:组件实际结构所在
<template>
<el-table :ref="modelValue" :data="tableData" border style="width: 100%" @selection-change="handleSelectionChange">
<el-table-column v-if="isMultiple" type="selection" width="55" />
<el-table-column v-for="(item, index) in tableNameList" :key="index" :prop="item.name" :label="item.label">
<template v-if="item.name==='status'" #default="scope">
<el-switch v-model="scope.row.status" @change="changeHandle(scope)"></el-switch>
</template>
</el-table-column>
<el-table-column label="操作" v-if="isNeedDelete==true || isNeedEdit==true">
<template #default="scope">
<el-button type="primary" v-if="isNeedEdit">修改</el-button>
<el-button type="danger" v-if="isNeedDelete">删除</el-button>
</template>
</el-table-column>
</el-table>
<slot name="buttons"></slot>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const multipleSelection = ref([])
const changeHandle = (e) => {
console.log(e)
}
const status = ref(true)
const props = defineProps({
isNeedDelete:{
type:Boolean,
default:false
},
isNeedEdit:{
type:Boolean,
default:false
},
isMultiple: {
type: Boolean,
default: false
},
tableNameList: {
type: Array
},
tableData: {
type: Object
},
modelValue: {
type: Array
}
})
const emit = defineEmits(['refChange'])
const handleSelectionChange = (val) => {
props.modelValue.value = val
//console.log(props.modelValue.value);
emit('refChange')
}
</script>
<style lang="less" scoped></style>
踩坑总结和下一个问题
提出的新问题:
本次的封装体验相对于第一次封装form组件顺畅了许多,也是最终实现了调用时通过v-bind指令决定操作按钮等功能的显示.但是依旧有很多问题:
1.对ts操作太不习惯,组件之间传参时会出现很多因为类型问题产生的红线,虽然不影响运行但实在不够规范,这也是急于解决的一个问题
2.封装的能用是能用,但是这种方式是最好的方式吗,element以及其他知名组件库是怎样进行实现的,下一步可能会尝试读一读element的代码
踩坑记录以及疑问:
1.在封装时多选框组件在变动时会提交一个代理数组,一开始是想在调用时传入一个数组然后再数据层监听来更新他(因为数据层是通过v-model来绑定,业务层变动会通知数据层变动),但实际上并没有能触发监听. 最终还是改成了传入方法然后用emit提交 所以疑问:为什么v-model绑定的响应式数据无法触发父组件中的监听
2.在封装中我想要实现在实际调用的时候通过插槽来传入表头 提交按钮等等东西,因此首次尝试了在多层结构中使用插槽,还是比较有趣的
3.记得之前看到过组件层级过深会影响程序的性能,想具体了解一下这个影响程度如何
4.在把多选框更改的数据从业务层传到调用层的过程中好像包裹了好多层,导致在调用时必须.value.value好几下,对于响应式数据和代理数据的理解还十分欠缺