源哥带你CodeReview03:提取table
讲解在同名B/D上都有,主要介绍一些跟业务无关的代码技巧
注: 在CodeReview中,部分内容主观性较大,一家之言姑且听之
本文主要介绍对table的基础封装,介绍下组件/业务组件/模块的区别
概览
ui组合组件 | 业务组件 | 动态/模块/复用 | |
---|---|---|---|
目标 | ui复用 | 业务封装 | 业务流程复用 |
对接 | ui/pm | 自己 | pm/后端 |
是否处理接口 | 不处理 | 可以 | 可以 |
减少data/methods | 无 | 可以 | 可以 |
自定义事件 | 很少 | 随意 | 模型相关 |
继承 | - | 无 | 继承某个ui组件 |
代码抽象
<template>
<div id="app">
<el-table v-loading="loading" :data="tableData" style="width: 100%">
<el-table-column prop="name" label="Name" min-width="180" />
</el-table>
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :page-size="10"
layout="total, prev, pager, next" :total="100">
</el-pagination>
<!-- <dialog> 等代码 -->
</div>
</template>
<script>
const api = (params) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve({
data: [{ name: Math.random() }, { name: Math.random() }],
page: params.page,
pageSize: 10,
total: 100
})
}, 1000)
})
}
export default {
data() {
return {
tableData: [{ name: 1 }, { name: 2 }],
pageSize: 10,
total: 100,
page: 1,
loading: false,
// 其他
}
},
methods: {
async getList() {
try {
this.loading = true
const res = await api({
page: this.page,
})
this.tableData = res.data
this.total = res.total
this.loading = false
} catch (error) {
this.loading = false
}
},
handleSizeChange(row) {
this.editRow = row;
this.editVisible = true;
},
handleCurrentChange(page) {
this.page = page
this.getList()
},
// 其他
}
}
</script>
封装table
上诉代码依然是一个文件当中,1000多行,我们可以将table+pagination 封装到一��组件当中
ui复用形
app
这是第一种封装方式,js部分全都不变,只对html进行封装
<template>
<div id="app">
<xxTable :loading="loading" :data="tableData" :total="total" :page="page" :pageSize="pageSize"
@size-change="handleSizeChange" @current-change="handleCurrentChange">
<el-table-column prop="name" label="Name" min-width="180" />
</xxTable>
<!-- <dialog> 等代码 -->
</div>
</template>
<script>
import xxTable from './components/xxTable'
const api = (params) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve({
data: [{ name: Math.random() }, { name: Math.random() }],
page: params.page,
pageSize: 10,
total: 100
})
}, 1000)
})
}
export default {
components: {
xxTable
},
data() {
return {
tableData: [{ name: 1 }, { name: 2 }],
pageSize: 10,
total: 100,
page: 1,
loading: false,
// 其他
}
},
methods: {
async getList() {
try {
this.loading = true
const res = await api({
page: this.page,
})
this.tableData = res.data
this.total = res.total
this.loading = false
} catch (error) {
this.loading = false
}
},
handleSizeChange(row) {
this.editRow = row;
this.editVisible = true;
},
handleCurrentChange(page) {
this.page = page
this.getList()
},
// 其他
}
}
</script>
xxTable
这种封装,类似于functional
,vue里的函数式/无状态组件,但本例中有$emit
,无法使用函数式组件的方式
<template>
<div>
<el-table v-loading="loading" :data="data" style="width: 100%">
<slot></slot>
</el-table>
<el-pagination
@size-change="$emit('size-change',$event)"
@current-change="$emit('current-change',$event)"
:page-size="10"
layout="total, prev, pager, next" :total="100">
</el-pagination>
</div>
</template>
<script>
export default {
props: ["loading","data","pageSize","total","page"]
}
</script>
总结
特点是js要写的内容一点都没少,还有一些变体
- 不用slot
一种是slot的内容放到xxTable里面,类似如下操作
<template >
<div>
<el-table v-loading="loading" :data="data" style="width: 100%">
<el-table-column prop="name" label="Name" min-width="180" />
</el-table>
<el-pagination
@size-change="$emit('size-change',$event)"
@current-change="$emit('current-change',$event)"
:page-size="pageSize"
:total="total"
layout="total, prev, pager, next" >
</el-pagination>
</div>
</template>
<script>
export default {
props: ["loading","data","pageSize","total","page"]
}
</script>
- 增加js内容
将html部分信息,转换为json/js,比如通过columns
,重新设置column
template中的内容少了,但是js里的内容多了,js中的columns有他对应的场景[动态columns,此处暂略],就目前来讲,属于浪费
<template>
<div id="app">
<xxTable :columns="columns" >
</xxTable>
</div>
</template>
<script>
export default {
// ...
computed: {
columns() {
return [{
prop: 'name',
label: 'name',
minWidth: "180"
}]
}
}
}
</script>
这种特点,属于ui组件
ui组件
注:此处开始扯概念了,主流前端没有统一,各有各的说法,一家之言,姑且听之
特点如下
- 主要跟ui/pm沟通
- 静态的,说什么状态,就有什么属性
- 原子型的
- 替代基础标签的
- 复用性极强,难度不高
- 没有业务价值
- 存放在底层的components中或者二方库
比如element-ui
就是这一类,我们这里使用的方式属于ui组件的嵌套,就是将两个ui组件合并成一个,将两个attrs和listeners放到同一个根节点下面,这种封装没有意义
业务复用型
不需要再slot中描述columns,在xxTable
中会描述具体的columns,可能会涉及一些js的操作
app
会考虑减少调用组件时传递的参数,但是没有标准,同一个人,每次写的都不一定一样
<template>
<div id="app">
<xxTable :loading="loading" :data="tableData" :queryParams="queryParams" @getData="getData">
</xxTable>
<!-- <dialog> 等代码 -->
</div>
</template>
<script>
import xxTable from './components/xxTable'
const api = (params) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve({
data: [{ name: Math.random() }, { name: Math.random() }],
page: params.page,
pageSize: 10,
total: 100
})
}, 1000)
})
}
export default {
components: {
xxTable
},
data() {
return {
tableData: [{ name: 1 }, { name: 2 }],
queryParams:{
pageSize: 10,
total: 100,
page: 1,
},
loading: false,
// 其他
}
},
methods: {
async getData() {
try {
this.loading = true
const res = await api({
page: this.queryParams.page,
})
this.tableData = res.data
this.queryParams.total = res.total
this.loading = false
} catch (error) {
this.loading = false
}
},
// 其他
}
}
</script>
xxTable
会考虑合并data,减少事件,但会多出组合的属性或事件,比如queryParams
和getData
<template >
<div>
<el-table v-loading="loading" :data="data" style="width: 100%">
<el-table-column prop="name" label="Name" min-width="180" />
</el-table>
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
:page-size="queryParams.pageSize" :total="queryParams.total" layout="total, prev, pager, next">
</el-pagination>
</div>
</template>
<script>
export default {
props: ["loading", "data", "queryParams"],
methods: {
handleSizeChange(row) {
},
handleCurrentChange(page) {
this.$emit("getData")
}
}
}
</script>
业务组件
具体如何实现与开发有关
- 会考虑减少js中传递的属性和函数
- 也可能会用到slot的性质
- 包含自定义属性
- 包含自定义事件
- 存放在当前目录下的components中
千奇百怪,也是最不好建议的组件
- 业务如此
- 不需要考虑
- 其他地方cv的
这种特点属于业务组件
,涉及到业务内容,可能只有这个业务才会用到,封装梳理的意义大于复用的意义
流程复用型
比业务组件考虑的更多的业务组件,有一套的方法论
组件 + 模型 === 流程复用/动态组件/模块
app
模型相关的内容全在xxTable
组件中,由他维护
但模型的定义需要在app中定义,这里将模型的定义简单的抽象为api
调用整个xxTable需要传递两部分,一部分是columns,一部分是如何如何获取数据,至此,所有的内容已传输完成,剩下的参数全在xxTable
中维护
<template>
<div id="app">
<xxTable :api="api" height="500px">
<el-table-column prop="name" label="Name" min-width="180" />
</xxTable>
<!-- <dialog> 等代码 -->
</div>
</template>
<script>
import xxTable from './components/xxTable'
const api = (params) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve({
data: [{ name: Math.random() }, { name: Math.random() }],
page: params.page,
pageSize: 10,
total: 100
})
}, 1000)
})
}
export default {
components: {
xxTable
},
methods: {
api,
// 其他
}
}
</script>
xxTable
额外的参数全跟模型有关,前段模型比较重,这里将其简化为 三个参数
- api
如何获取数据
- queryParams
请求参数
- autoLoad
是否自动初始化数据
<template >
<div>
<el-table v-loading="loading" :data="data" v-bind="$attrs" v-on="$listeners" >
<el-table-column prop="name" label="Name" min-width="180" />
</el-table>
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :page-size="pageSize"
:total="total" layout="total, prev, pager, next">
</el-pagination>
</div>
</template>
<script>
export default {
props: ["api", "queryParams", "autoLoad"],
data() {
return {
data: [],
// 本地存储的数据源
queryParamsLocal: {
pageSize: 10,
total: 100,
page: 1,
},
loading: false,
}
},
beforeMount() {
if (this.autoLoad !== false) {
this.getData()
}
},
computed: {
// 用默认值或代理都可以
// 针对传递的是json,但数据不全的情况下,需要特殊处理
// 可抽取为一个utils
pageSize: {
get() {
return this.queryParams?.pageSize || this.queryParamsLocal.pageSize
},
set(value) {
Object.hasOwnProperty.call(this.queryParams || {}, "pageSize") ? this.queryParams.pageSize = value : this.queryParamsLocal.pageSize = value
}
},
total: {
get() {
return this.queryParams?.total || this.queryParamsLocal.total
},
set(value) {
Object.hasOwnProperty.call(this.queryParams || {}, "total") ? this.queryParams.total = value : this.queryParamsLocal.total = value
}
},
page: {
get() {
return this.queryParams?.page || this.queryParamsLocal.page
},
set(value) {
Object.hasOwnProperty.call(this.queryParams || {}, "page") ? this.queryParams.page = value : this.queryParamsLocal.page = value
}
}
},
methods: {
handleSizeChange(row) {
},
handleCurrentChange(page) {
this.page = page
this.getData()
},
// 对外提供
async getData() {
try {
this.loading = true
const res = await this.api({
pageSize: this.pageSize,
total: this.total,
page: this.page
})
this.data = res.data
this.loading = false
} catch (error) {
this.loading = false
}
}
}
}
</script>
流程复用
- 继承某个核心UI组件
比如table中,根节点的attrs
与listeners
全部指向table,同理,也会涉及到方法对外暴露,上面的代码并没有处理
- 前端模型 + ui组件
- 动态状态
- 实现后端/pm的需求
- 只考虑布局,一般不考虑ui细节
注:代码只是其中一种实现,具体细节有很多都是不严谨的
转载自:https://juejin.cn/post/7372469848342904866