在开发管理后台过程中,一定会遇到不少了增删改查页面,而这些页面的逻辑大多都是相同的,这样子会导致代码耦合度越来越高,下面,我们将这些可复用的数据抽离出来成hook,既解决耦合度的问题又提高工作效率。

在开发管理后台过程中,一定会遇到不少了增删改查页面,而这些页面的逻辑大多都是相同的,如获取列表数据,分页,筛选功能这些基本功能。而不同的是呈现出来的数据项。还有一些操作按钮。【相关推荐:vuejs视频教程、web前端开发】

对于刚开始只有 1,2 个页面的时候大多数开发者可能会直接将之前的页面代码再拷贝多一份出来,而随着项目的推进类似页面数量可能会越来越多,这直接导致项目代码耦合度越来越高。
立即学习“前端免费学习笔记(深入)”;
这也是为什么在项目中一些可复用的函数或组件要抽离出来的主要原因之一
下面,我们封装一个通用的useList,适配大多数增删改查的列表页面,让你更快更高效的完成任务,准点下班 ~

前置知识
封装
我们需要将一些通用的参数和函数抽离出来,封装成一个通用hook,后续在其他页面复用相同功能更加简单方便。
定义列表页面必不可少的分页数据
export default function useList() {
// 加载态
const loading = ref(false);
// 当前页
const curPage = ref(1);
// 总数量
const total = ref(0);
// 分页大小
const pageSize = ref(10);
}如何获取列表数据
思考一番,让useList函数接收一个listRequestFn参数,用于请求列表中的数据。
定义一个list变量,用于存放网络请求回来的数据内容,由于在内部无法直接确定列表数据类型,通过泛型的方式让外部提供列表数据类型。
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丢到请求函数即可。
在 useList 函数中,第二个参数接收一个filterOption对象,对应列表中的筛选条件字段。
该软件是以ecshop作为核心的仿制万表网的商场网站源码。万表网模板 2015最新版整体简洁大气,功能实用,是一款时尚典雅的综合类模板!样式精美的商品分类树,层次分明,分类结构一目了然。首页轮播主广告分别对应切换小广告,商品宣传更到位。独家特色增加顶级频道页面、品牌页面,以及仿京东对比功能,提升网站档次,让您的网站更加高端大气!并且全站采用div+css布局,兼容性良好,更注重页面细节,增加多种j
调整一下loadData函数,在请求函数中传入filterOption对象即可
注意,传入的 listRequestFn 函数接收的参数数量和类型是否正常对应上 请根据实际情况进行调整
export default function useList< ItemType extends Object, FilterOption extends Object >(listRequestFn: Function, filterOption: Ref
注意,这里 filterOption 参数类型需要的是 ref 类型,否则会丢失响应式 无法正常工作
清空筛选器字段
在页面中,有一个重置的按钮,用于清空筛选条件。这个重复的动作可以交给 reset 函数处理。
通过使用 Reflect 将所有值设定为undefined,再重新请求一次数据。
什么是 Reflect?看看这一篇文章Reflect 映射对象
export default function useList< ItemType extends Object, FilterOption extends Object >(listRequestFn: Function, filterOption: Ref
导出功能
除了对数据的查看,有些界面还需要有导出数据功能(例如导出 csv,excel 文件),我们也把导出功能写到useList里
通常,导出功能是调用后端提供的导出Api获取一个文件下载地址,和loadData函数类似,从外部获取exportRequestFn函数来调用Api
在函数中,新增一个exportFile函数调用它。
export default function useList< ItemType extends Object, FilterOption extends Object >( listRequestFn: Function, filterOption: Ref
注意,传入的 exportRequestFn 函数接收的参数数量和类型是否正常对应上 请根据实际情况进行调整
优化
现在,整个useList已经满足了页面上的需求了,拥有了获取数据,筛选数据,导出数据,分页功能
还有一些细节方面,在上面所有代码中的try..catch中的catch代码片段并没有做任何的处理,只是简单的console.log一下
提供钩子
在useList新增一个 Options 对象参数,用于函数成功、失败时执行指定钩子函数与输出消息内容。
定义 Options 类型
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<
ItemType extends Object,
FilterOption extends Object
>(
listRequestFn: Function,
filterOption: Ref设置Options默认值
const DEFAULT_MESSAGE = {
GET_DATA_IF_FAILED: "获取列表数据失败",
EXPORT_DATA_IF_FAILED: "导出数据失败",
};
const DEFAULT_OPTIONS: OptionsType = {
message: DEFAULT_MESSAGE,
};
export default function useList<
ItemType extends Object,
FilterOption extends Object
>(
listRequestFn: Function,
filterOption: Ref在没有传递钩子的情况霞,推荐设置默认的失败时信息显示
优化loadData,exportFile函数
基于 elementui 封装 message 方法
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" });
}loadData 函数
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;
}
};exportFile 函数
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 使用方法
筛选 重置 {{ scope.row.name }} {{ scope.row.mobile || "未绑定手机号码" }} {{ scope.row.email || "未绑定邮箱地址" }} 详情
本文useList的完整代码在 github.com/QC2168/snip…










