页码列表组件封装思路
刀刀
4/8/2025
0 字
0 分钟
- UP主:三十的前端课,视频地址:前往学习
前言
项目中封装业务组件两大原则:
不结合具体业务逻辑
- 组件只作为数据的容器,数据统一父组件传入。
- 只编写 UI 逻辑,具体的数据操作这样的业务逻辑,触发父组件的监听交给父组件处理
尽量的提供简便
在不结合具体业务逻辑的前提下,让父组件使用组件尽可能方便,原则是观察大部分页面的设计,能方便满足大多数页面的需求。少数页面有差距,也有扩展方案。
组件搭建
本案例以 element-plus
组件库中的表格和分页器组件为基础,做初步搭建。
html
<script setup>
import { ref } from "vue";
const { tableData, total } = defineProps({
tableData: {
type: Array,
},
total: {
type: Number,
},
});
const emit = defineEmits(["changePage", "changeSize"]);
// 页码参数对象
const pageParams = ref({
page: 1,
pageSize: 10,
});
// 页码切换让父组件去做处理
const changePage = (e) => {
pageParams.value.page = val;
emit("changePage", pageParams.value);
};
// 单页码数据数量切换让父组件去做处理
const changeSize = (e) => {
pageParams.value.pageSize = val;
emit("changeSize", pageParams.value);
};
</script>
<template>
<el-table>
<!-- 子组件无法控制渲染什么内容,流出插槽让父组件传入 -->
<slot>
<el-table-column
v-for="(item, index) in tableColumn"
:key="index"
:prop="item.prop"
:label="item.label"
></el-table-column>
</slot>
</el-table>
<el-pagination
:total="total"
:layout="(prev, pager, next, sizes)"
@current-change="changePage"
@size-change="changeSize"
/>
</template>
现在已经尽可能的简便来搭建组件了,但是父组件用起来还是很麻烦,和不封装直接用没什么区别。
因此现在要观察大部分的页面和接口,加入部分业务逻辑,使其满足大部分页面。但也能让部分极端情况满足扩展自定义。
翻页逻辑
子组件修改页码组件让父组件更方便:
- 单页码数据数量切换给定一个默认值,但是也要支持父组件扩展自定义传入使用
- 后端接口一般要保持统一性,页码一般都是一样的单词,不会突然改变。因此父组件无需监听两个事件,使用一个事件即可
vue
<script setup>
import { ref } from "vue";
const { tableData, total, pageSizes } = defineProps({
tableData: {
type: Array,
},
total: {
type: Number,
},
pageSizes: {
type: Array,
default: () => [10, 20, 30],
},
});
const emit = defineEmits(["getPage"]);
// 页码参数对象
const pageParams = ref({
page: 1,
pageSize: 10,
});
// 分页器触发切换让父组件去做处理
const changePage = (e) => {
pageParams.value.page = val;
emit("getPage", pageParams.value);
};
</script>
<template>
<el-table>
<!-- 子组件无法控制渲染什么内容,流出插槽让父组件传入 -->
<slot></slot>
</el-table>
<el-pagination
:total="total"
:layout="(prev, pager, next, sizes)"
:page-size="pageSize[0]"
:page-sizes="pageSizes"
@current-change="getPage"
@size-change="getPage"
/>
</template>
父组件传入对应的参数:
vue
<script setup>
import { onMounted } from "vue";
import PageList from "./components/pageList.vue";
import axios from "axios";
const initList = async (e) => {
axios
.get("http://xxxxx", {
params: { ...e },
})
.then((res) => {
console.log(res);
});
};
const getPage = (e) => {
initList(e);
};
onMounted(() =>
initList({
page: 1,
pageSize: 10,
})
);
</script>
<template>
<page-list
:tableData="tableData"
:total="400"
:pageSizes="[50, 100, 150]"
@getPage="getPage"
/>
</template>
表格子行
父组件要使用这个封装的组件时,需要通过插槽设置表格行,代码如下所示:
vue
<template>
<page-list :tableData="tableData" @getPage="getPage">
<el-table-column prop="name" label="名称"></el-table-column>
<el-table-column prop="price" label="价格"></el-table-column>
<el-table-column label="操作">
<el-button>删除</el-button>
<el-button>编辑</el-button>
</el-table-column>
</page-list>
</template>
但是这么写并不是很方便,需要为插槽提供大量的代码。
如果需要简化,就先观察大部分页面表格的使用。大多数都是表格渲染数组数据,然后有需要的话加上操作。
因此可以不用再传插槽了,直接通过传递一个数组变量,里面为多个对象,每个对象提供 prop
和 name
属性。子组件接收遍历渲染即可。代码如下:
父组件:
vue
<script setup>
const tableColumn = [
{
prop: "name",
name: "名称",
},
{
prop: "price",
name: "价格",
},
];
</script>
<template>
<page-list
:tableData="tableData"
@getPage="getPage"
:tableColumn="tableColumn"
/>
</template>
子组件在默认插槽中获取并循环遍历。
注意
这里用默认插槽是因为还需要考虑部分极端情况,要提供其他具名插槽给父组件使用
vue
<script setup>
import { ref } from "vue";
const { tableColumn } = defineProps({
// ...
tableColumn: {
type: Array,
default: () => [10, 20, 30],
},
});
// ...
</script>
<template>
<el-table>
<!-- 子组件无法控制渲染什么内容,流出插槽让父组件传入 -->
<slot>
<el-table-column
v-for="(item, index) in tableColumn"
:key="index"
:prop="item.prop"
:label="item.label"
></el-table-column>
<!-- 假设每个页面都要有删除和编辑按钮(更精细点应该提供变量让父组件设置需不需要 -->
<el-table-column label="操作">
<el-button>删除</el-button>
<el-button>编辑</el-button>
</el-table-column>
</slot>
</el-table>
<!-- ... -->
</template>
在极端情况下,父组件想要也使用自己的按钮,此时需要支持父组件传入按钮数组,子组件循环遍历。
父组件代码如下所示:
vue
<script setup>
const buttonList = [
{
emit: "stop",
name: "禁用",
type: "danger",
},
];
// 子组件点击禁用按钮
const stopFn = (row) => {
console.log(row);
};
// ...
</script>
<template>
<page-list
:buttonList="buttonList"
:tableData="tableData"
@getPage="getPage"
:tableColumn="tableColumn"
@stop="stopFn"
/>
</template>
子组件做如下操作:
- 获取数组循环遍历渲染
- 为按钮绑定点击事件,传入该行的数据以及按钮的自定义事件
代码如下:
vue
<script setup>
import { ref } from "vue";
const { tableColumn, buttonList } = defineProps({
// ...
tableColumn: {
type: Array,
default: () => [10, 20, 30],
},
buttonList: {
type: Array,
default: () => [],
},
});
// 点击操作模块的按钮
const handleClickFn = (emitName, row) => {
emit(emitName, row);
};
// ...
</script>
<template>
<el-table>
<!-- 子组件无法控制渲染什么内容,流出插槽让父组件传入 -->
<slot>
<el-table-column
v-for="(item, index) in tableColumn"
:key="index"
:prop="item.prop"
:label="item.label"
></el-table-column>
<!-- 假设每个页面都要有删除和编辑按钮(更精细点应该提供变量让父组件设置需不需要 -->
<el-table-column label="操作">
<template #default="scope">
<el-button>删除</el-button>
<el-button>编辑</el-button>
<el-button
v-for="item in buttonList"
:key="item.emit"
@click="handleClickFn(item.emit, scope)"
>{{ item.name }}</el-button
>
</template>
</el-table-column>
</slot>
</el-table>
<!-- ... -->
</template>
总结
- 组件只作为数据容器,数据父组件传入
- 只做最简单的逻辑,深层次逻辑由父组件处理
- 尽可能让父组件使用简便,但也要考虑特殊情况