前言
在开发和管理后台的过程中,肯定会遇到很多增删改查的页面,而那些页面的逻辑大多是相同的,比如获取列表数据、分页等基本功能,和过滤功能。 区别在于所呈现的数据项。 还有一些操作按钮。
当一开始只有1、2个页面时,大多数开发者可能会直接复制上一个页面的代码,而随着项目的进展,类似页面的数量可能会增加,这直接导致项目代码耦合度越来越高。
这也是项目中应该抽取一些可复用的功能或者组件的主要原因之一
下面,我们封装了一个通用的useList,适配大部分列表页面的增删改查,让你更快更高效的完成任务,准时上班~
后知识封装
我们需要提取一些通用的参数和函数,并将它们封装到一个通用的钩子中。 以后在其他页面复用相同的功能会更简单、更方便。
定义列表页面的基本分页数据
export default function useList() {
// 加载态
const loading = ref(false);
// 当前页
const curPage = ref(1);
// 总数量
const total = ref(0);
// 分页大小
const pageSize = ref(10);
}
如何获取列表数据
想了想,让useList函数接收一个listRequestFn参数来请求列表中的数据。
定义一个列表变量来存储网络请求的数据内容,因为内部很难直接确定列表数据类型,而让外部以形参的形式提供列表数据类型。
export default function useList(
listRequestFn: Function
) {
// 忽略其他代码
const list = ref([]);
}
在useList中创建loadData函数来调用数据获取函数。 该函数接收一个参数,获取指定页码的数据(可选,默认为curPage的值)。
设置加载状态调用外部传入的函数,将获取到的数据参数放入list和total中即可关闭加载状态
这里使用的是async/await句型。 如果请求或者解构出现错误,就会使用catch代码块,然后关闭加载状态。
这里需要注意的是传入listRequestFn函数接收的参数个数和类型对应正常,请根据实际情况调整
export default function useList(
listRequestFn: Function
) {
// 忽略其他代码
// 数据
const list = ref([]);
// 过滤数据
// 获取列表数据
const loadData = async (page = curPage.value) => {
// 设置加载中
loading.value = true;
try {
const {
data,
meta: { total: count },
} = await listRequestFn(pageSize.value, page);
list.value = data;
total.value = count;
} catch (error) {
console.log("请求出错了", "error");
} finally {
// 关闭加载中
loading.value = false;
}
};
}
别忘了,还有切换分页来处理
使用watch函数窃听数据,当curPage和pageSize的值发生变化时,调用loadData函数获取新数据。
export default function useList(
listRequestFn: Function
) {
// 忽略其他代码
// 监听分页数据改变
watch([curPage, pageSize], () => {
loadData(curPage.value);
});
}
现已实现基本列表数据获取
实施数据过滤器
在庞大的数据列表中,数据过滤是必不可少的功能
一般我会在一个ref中定义过滤条件数组,请求的时候直接把ref扔到request函数中即可。
useList函数中,第二个参数接收一个filterOption对象,对应列表中的过滤条件数组。
调整loadData函数,在request函数中传入filterOption对象
注意传入listRequestFn函数接收的参数个数和类型对应正常,请根据实际情况调整
export default function useList(listRequestFn: Function, filterOption: Ref
注意这里的filterOption参数类型需要是ref类型,否则会失去响应能力而无法正常工作
清除过滤器阵列
页面上有一个重置按钮,用于清除过滤条件。 这种重复动作可以通过重置功能来处理。
使用Reflect将所有值设置为undefined,并再次请求数据。
哪些是 Reflect?查看这篇文章 Reflect Mapping Objects
export default function useList(listRequestFn: Function, filterOption: Ref
导入功能
不仅仅是数据查看,有些界面还需要有导入数据的功能(比如导入csv、excel文件),我们在useList中也引入了导入功能
一般导入函数是调用前端提供的导入Api获取文件下载地址,类似loadData函数,从外部获取exportRequestFn函数调用Api
在函数中,添加一个新的exportFile函数来调用它。
export default function useList(
listRequestFn: Function,
filterOption: Ref
注意导入的exportRequestFn函数接收的参数个数和类型与正常对应,请根据实际情况调整
优化
现在整个useList已经满足了页面上的需求,并且具备了获取数据、过滤数据、导入数据、分页的功能
还有一些细节,所有代码中的try..catch中的catch代码片段并没有做任何处理,只是简单的console.log
提供挂钩
在useList中添加一个Options对象参数,用于执行指定的钩子函数elementui 分页组件,并在函数成功或失败时输出消息内容。
定义选项类型
export interface MessageType {
GET_DATA_IF_FAILED?: string;
GET_DATA_IF_SUCCEED?: string;
EXPORT_DATA_IF_FAILED?: string;
EXPORT_DATA_IF_SUCCEED?: string;
}
export interface OptionsType {
requestError?: () => void;
requestSuccess?: () => void;
message: MessageType;
}
export default function useList(
listRequestFn: Function,
filterOption: Ref
设置选项默认值
const DEFAULT_MESSAGE = {
GET_DATA_IF_FAILED: "获取列表数据失败",
EXPORT_DATA_IF_FAILED: "导出数据失败",
};
const DEFAULT_OPTIONS: OptionsType = {
message: DEFAULT_MESSAGE,
};
export default function useList(
listRequestFn: Function,
filterOption: Ref
在没有delivery hook的情况下elementui 分页组件,建议设置默认显示错误信息
优化loadData、exportFile函数
基于elementui封装消息方法
import { ElMessage, MessageOptions } from "element-plus";
export function message(message: string, option?: MessageOptions) {
ElMessage({ message, ...option });
}
export function warningMessage(message: string, option?: MessageOptions) {
ElMessage({ message, ...option, type: "warning" });
}
export function errorMessage(message: string, option?: MessageOptions) {
ElMessage({ message, ...option, type: "error" });
}
export function infoMessage(message: string, option?: MessageOptions) {
ElMessage({ message, ...option, type: "info" });
}
加载数据函数
const loadData = async (page = curPage.value) => {
loading.value = true;
try {
const {
data,
meta: { total: count },
} = await listRequestFn(pageSize.value, page, filterOption.value);
list.value = data;
total.value = count;
// 执行成功钩子
options?.message?.GET_DATA_IF_SUCCEED &&
message(options.message.GET_DATA_IF_SUCCEED);
options?.requestSuccess?.();
} catch (error) {
options?.message?.GET_DATA_IF_FAILED &&
errorMessage(options.message.GET_DATA_IF_FAILED);
// 执行失败钩子
options?.requestError?.();
} finally {
loading.value = false;
}
};
导出文件函数
const exportFile = async () => {
if (!exportRequestFn) {
throw new Error("当前没有提供exportRequestFn函数");
}
if (typeof exportRequestFn !== "function") {
throw new Error("exportRequestFn必须是一个函数");
}
try {
const {
data: { link },
} = await exportRequestFn(filterOption.value);
window.open(link);
// 显示信息
options?.message?.EXPORT_DATA_IF_SUCCEED &&
message(options.message.EXPORT_DATA_IF_SUCCEED);
// 执行成功钩子
options?.exportSuccess?.();
} catch (error) {
// 显示信息
options?.message?.EXPORT_DATA_IF_FAILED &&
errorMessage(options.message.EXPORT_DATA_IF_FAILED);
// 执行失败钩子
options?.exportError?.();
}
};
如何使用useList
<el-collapse-item title="筛选条件" name="1">
筛选
重置
{{ scope.row.name }}
{{ scope.row.mobile || "未绑定手机号码" }}
{{ scope.row.email || "未绑定邮箱地址" }}
详情
0" class="flex justify-end mt-4">
import { UserInfoApi } from "@/network/api/User";
import useList from "@/lib/hooks/useList/index";
const filterOption = ref({});
const {
list,
loading,
reset,
filter,
curPage,
pageSize,
reload,
total,
loadData,
} = useList(
UserInfoApi.list,
filterOption
);
本文useList的完整代码在github.com/QC2168/snip...
如果你对这个hook有更好的建议,欢迎pr或者评论区留言
另外,为了在日常开发中节省查找打包代码片段的时间,提高工作效率(touch time++),仓库还存储了一些第三方打包代码片段✨,方便大家取用(持续更新~~ )