工具类-列表请求工具 useList
useList
用于列表请求的基于 vue 3 的 hooks,接收请求函数、请求参数等数据,自动生成请求请求函数,分页信息等
本文有涉及到 http 请求工具和接口返回格式的内容:
- http 工具:一个基于 axios 封装的请求工具
- ResponseData 接口:定义接口返回数据结构的 interface
interface ResponseData<T = any> {code: number;data: T;message: string;
}
详情可参考文章:工具类-基于 axios 的 http 请求工具 Request
实现一个简单的列表请求
import { ref, Ref } from 'vue';
import { get, cloneDeep } from 'lodash-es';
import { ResponseData } from '@/http/type';interface UseListConfig<P = any, T = any> {request: {/*** 请求列表方法*/api: (params: P) => Promise<ResponseData<T[]>>;/*** 请求参数*/params?: P;};response?: {/*** 列表数据 默认 data* 例: 响应数据为 { data: { list: [] } } 则传递 data.list;*/listDataKey?: string;};
}export function useList<P extends object = any, T = any>(config: UseListConfig<P, T>) {const cacheConfig = cloneDeep(config);const { api } = cacheConfig.request;const { listDataKey = 'data' } = cacheConfig.response || {};const params = ref(cloneDeep(cacheConfig.request.params || {}) as P) as Ref<P>;const list = ref([]) as Ref<T[]>;const handleSearch = async () => {const res = await api(params.value as P);// 更具 listDataKey 获取列表数据list.value = get(res, listDataKey);return res;};return {params,list,handleSearch,};
}
useList 定义了两个泛型,其中 P 代表请求的参数类型,T 代表列表数据每一项的类型,接收请求方法和请求参数等数据, 最后返回了请求参数变量 params,列表数据 list,还有发起列表请求的 handlerSearch 方法
在 vue 3 中使用
script
import { useList } from '@/hooks/-useList';
import { ResponseData } from '@/http/type';// 定义请求参数类型
interface GetListParams {name: string;
}
// 定义请求项类型
interface ListItem {id: number;name: string;
}// 模拟列表数据和 http 请求
const MOCK_LIST: ListItem[] = Array(100).fill(0).map((_, index) => ({id: index + 1,name: `list-item-${index + 1}`,}));
const getList = async (params: GetListParams): Promise<ResponseData<ListItem[]>> => {await new Promise(resolve => setTimeout(resolve, 3 * 1e3));const list: ListItem[] = [];if (params.name) list.push(...MOCK_LIST.filter(item => item.name.includes(params.name)));else list.push(...MOCK_LIST);return {code: 200,data: list,message: 'success',};
};const { params, list, handleSearch } = useList<GetListParams, ListItem>({request: {api: getList,params: {name: '',},},response: {listDataKey: 'data',},
});
template
<template><div><div><a-input v-model="params.name" placeholder="请输入名称" /><a-button type="primary" @click="handleSearch()">查询</a-button></div><div><p v-for="item of list" :key="item.id">{{ item.name }}</p></div></div>
</template>
使用泛型定义好请求的参数和返回的内容的类型,向 useList 传入请求函数和参数,获得 params,list,以及 handlerSearch,将 params 的字段绑定到搜索的表单元素,点击搜索调用 handlerSearch 即可完成列表的请求
增加 loading
每次列表请求时需要给 列表增加一个加载中的文案或图表,每次手动去声明一个 loading,在 调用 handleSearch 前赋值为 true,调用结束后赋值为 false,可以实现控制列表的加载状态。
但是每个列表都要实现一遍过于麻烦和冗余,可以在 useList 中增加一个 loading 的变量并返回,在请求前后改变 loading 的值,实现加载状态的控制。
loading 的实现使用了 useLoading,可以查阅 工具类-useLoading
export function useList<P extends object = any, T = any>(config: UseListConfig<P, T>) {...const { loading, executor } = useLoading();const handleSearch = async () => {const res = await executor(async () => api(params.value as P));list.value = get(res, listDataKey);return res;};return {...loading,...}
}
使用示例
script
...
const { params, loading, list, handleSearch } = useList<GetListParams, ListItem>({request: {api: getList,params: {name: '',},},
});
template
<template><div><div><a-input v-model="params.name" placeholder="请输入名称" /><a-button type="primary" @click="handleSearch()">查询</a-button></div><!-- v-loading 是使元素显示加载状态的指令 --><div v-loading="loading"><p v-for="item of list" :key="item.id">{{ item.name }}</p></div></div>
</template>
处理分页信息
首先增加 UseListConfig 的分页信息类型定义
interface UseListConfig<P = any, T = any> {request: {.../*** 分页信息-当前页数参数在 params 中的 key* 默认: page*/pageNumKey?: string;/*** 分页信息-每页条数参数在 params 中的 key* 默认: pageSize*/pageSizeKey?: string;};response?: {.../*** 总条数字段的 key* 例: 响应数据为 { data: { list: [], total: 0 } } 则传递 data.total;* 默认 pageInfo.items*/listDataKey?: string;};
}
在 handleSearch 中增加分页控制,增加 handleCurrentChange 和 handleSizeChange 方法
export function useList<P extends object = any T = any>(config: UseListConfig<P, T>) {...const total = ref(0);const handleSearch = async (pageNum = 1) => {if (pageNumKey in (params.value as object)) {(params.value as any)[pageNumKey] = pageNum;}const res = await executor(async () => api(params.value as P));list.value = get(res, listDataKey);total.value = get(res, totalKey);return res;};/*** 切换当前页码 刷新列表*/const handleCurrentChange = async (pageNum: number) => {await handleSearch(pageNum);};/*** 切换分页大小 刷新列表*/const handleSizeChange = async (pageSize: number) => {if (pageSizeKey in (params.value as object)) {(params.value as any)[pageSizeKey] = pageSize;}// 切换分页大小后,默认回到第一页await handleSearch(1);};return {...handleSearch,handleCurrentChange,handleSizeChange,}
}
使用示例
script
import { useList } from '@/hooks/-useList';
import { ResponseData } from '@/http/type';
import Pagination from '@/components/pagination/index.vue'; // 分页的组件interface GetListParams {name: string;page: number;pageSize: number;
}interface ListItem {id: number;name: string;
}// 模拟列表数据和 http 请求
const MOCK_LIST: ListItem[] = Array(100).fill(0).map((_, index) => ({id: index + 1,name: `list-item-${index + 1}`,}));
const getList = async (params: GetListParams): Promise<ResponseData<ListItem[]>> => {await new Promise(resolve => setTimeout(resolve, 3 * 1e3));let list: ListItem[] = [];if (params.name) list = MOCK_LIST.filter(item => item.name.includes(params.name));else list = MOCK_LIST;list = list.slice(params.page * params.pageSize - params.pageSize, params.page * params.pageSize);return {code: 200,data: list,message: 'success',pageInfo: {items: MOCK_LIST.length,},};
};const { params, total, loading, list, handleSearch, handleCurrentChange, handleSizeChange } =useList<GetListParams, ListItem>({request: {api: getList,params: {name: '',page: 1,pageSize: 10,},pageNumKey: 'page',pageSizeKey: 'pageSize',},response: {listDataKey: 'data',totalKey: 'pageInfo.items',},});
template
<template><div><div><a-input v-model="params.name" placeholder="请输入名称" /><a-button type="primary" @click="handleSearch()">查询</a-button><pagination:total="total":page-size="params.pageSize":current="params.page":show-total="true"@change="handleCurrentChange"@page-size-change="handleSizeChange"/></div><!-- v-loading 是使元素显示加载状态的指令 --><div v-loading="loading"><p v-for="item of list" :key="item.id">{{ item.name }}</p></div></div>
</template>
增加 reset 方法
在列表请求页中,经常有需要清空或重置搜索条件的需求,可以在 useList 中记录传入的初始 params,增加 handleReset 函数,将 params 变量的值赋值为 初始的 params 值
export type DeepReadonly<T> = {readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};export function useList<P extends object = any, T = any>(config: UseListConfig<P, T>) {...// 使用 readonly 约束 defaultParams,避免被更改const defaultParams: DeepReadonly<P> = cloneDeep(cacheConfig.request.params || ({} as P));const handleReset = () => {params.value = cloneDeep(defaultParams as P);handleSearch(); // 重置完立即发起搜索};return {...handleReset,}
}
有可能 rest 函数不一定是将 params 变量重置为使用 useList 时传入的值,为了应付这种特殊情况,我们可以增加一个 handleCustomeReset 的参数,将重置的权限暴露出去
interface UseListConfig<P = any, T = any> {request: {.../*** 自定义重置方法*/handleCustomReset?: (params: P, defaultParams: DeepReadonly<P>) => P;;};
}export function useList<P extends object = any, T = any>(config: UseListConfig<P, T>) {...const defaultParams = cloneDeep(cacheConfig.request.params || ({} as P));const { handleCustomReset } = cacheConfig.request;const handleReset = () => {if (handleCustomReset) params.value = handleCustomReset(params.value, defaultParams);else params.value = cloneDeep(defaultParams as P);handleSearch(); // 重置完立即发起搜索};return {...handleReset,}
}
增加 handleCustomReset 的可选项, 将当前 params 的值和 defaultParmas 传给 handleCustomReset,如果有传入 handleCustomReset,则重置时使用 handleCustomReset 的返回值赋值给 params 变量,若没有 handleCustomReset,则使用默认的重置方式
增加一些回调钩子
请求前的钩子
- handleValidate: 请求前校验,校验参数是否合理等
- handleParams:请求前处理参数
- resetApi: 重置列表请求方法
请求完成后的钩子
- handleResponseData:处理返回的列表数据
interface UseListConfig<P = any, T = any> {request: {.../*** 自定义重置方法*/handleCustomReset?: (params: P, defaultParams: DeepReadonly<P>) => P;/*** 校验函数,校验参数是否合理等* 返回 false 则不发起请求*/handleValidate?: (params: DeepReadonly<P>) => boolean;/*** 处理请求参数*/handleParams?: (params: DeepReadonly<P>) => P;/*** 重置请求方法*/resetApi?: (params: DeepReadonly<P>) => (params: P) => Promise<ResponseData<T[]>>;};response?: {.../*** 处理响应数据*/handleResponseData?: (list: T[]) => T[];};
}export function useList<P extends object = any, T = any>(config: UseListConfig<P, T>) {...const {handleValidate,handleParams,resetApi,} = cacheConfig.request || {};const {handleResponseData,} = cacheConfig.response || {};const handleSearch = async (pageNum = 1) => {if (pageNumKey in params.value) {(params.value as any)[pageNumKey] = pageNum;}let _params = cloneDeep(params.value);if (handleValidate && !handleValidate(_params)) return;if (handleParams) _params = handleParams(_params);if (resetApi) api = resetApi(_params);const res = await executor(async () => api(params.value as P));const _list = get(res, listDataKey);if (handleResponseData) list.value = handleResponseData(_list);else list.value = _list;total.value = get(res, totalKey);return res;};
}
使用示例
const getList = async (params: GetListParams): Promise<ResponseData<ListItem[]>> => {console.log('getList');await new Promise(resolve => setTimeout(resolve, 3 * 1e3));...
};
const getListLongTime = async (params: GetListParams): Promise<ResponseData<ListItem[]>> => {console.log('getListLongTime');await new Promise(resolve => setTimeout(resolve, 10 * 1e3));...
};const { params, total, loading, list, handleSearch, handleCurrentChange, handleSizeChange } =useList<GetListParams, ListItem>({request: {...handleValidate(params) {if (params.page <= 0) {console.error('page 必须大于 0');return false;}return true;},handleParams(params) {return {...params,pageSize: Math.min(params.pageSize, 10), // 当 pageSize 小于 10 时,默认设置为 10};},resetApi(params) {// 根据 params.name 判断调用哪个接口if (params.name.toLocaleLowerCase() === 'longtime') return getListLongTime;return getList;},},response: {...handleResponseData(list) {// 将 list 中的 name 转为大写return list.map(item => ({...item,name: item.name.toUpperCase(),}));},},});
增加防抖
在 UseListConfig 中增加 lazy 字段,接收一个以毫秒为单位的时间值作为搜索时函数的防抖时间
interface UseListConfig<P = any, T = any> {request: {.../*** 搜索函数防抖延迟时间* 默认不开启防抖*/lazy?: number;...};response?: {...};
}
先实现一个用于防抖的函数,类似 lodash 的 debounce 函数
注:为什么不直接用 ladash 的 debounce,因为 debounce 的返回值类型不太符合需求
type AnyFunction = (...args: any[]) => any;const debounce = <T extends AnyFunction>(fn: T, lazy = 300): ((...args: Parameters<T>) => Promise<ReturnType<T>>) => {let timer: number | null = null;return (...args) =>new Promise(resolve => {if (timer) clearTimeout(timer);timer = window.setTimeout(() => {resolve(fn(...args));}, lazy);});
};
export function useList<P extends object = any, T = any>(config: UseListConfig<P, T>) {...const {...lazy,} = cacheConfig.request;...const _handleSearch = async (pageNum = 1) => {const res = await api(params.value); // 去掉了 Loading 的 executor 函数执行...};const handleSearch = async (pageNum = 1) => {const func = lazy ? debounce(_handleSearch, lazy) : _handleSearch;// 在这里执行 Loading 的 executor 函数,因为在防抖时间内也需要显示 Loading 状态return executor(func, pageNum);};...
}
相关文章:
工具类-列表请求工具 useList
useList 用于列表请求的基于 vue 3 的 hooks,接收请求函数、请求参数等数据,自动生成请求请求函数,分页信息等 本文有涉及到 http 请求工具和接口返回格式的内容: http 工具:一个基于 axios 封装的请求工具Response…...
Scala中的正则表达式01
规则类型具体规则示例说明单字符大多数字符匹配自身正则表达式 abc,文本 abca 匹配 a,b 匹配 b,c 匹配 c方括号 [ ][ ] 定义字符集,匹配其一[abc],文本 a、b 或 c[abc] 匹配 a、b 或者 c排除字符集 [^ ][^ ] 开头加 ^&…...
基于SpringBoot的养老院管理系统的设计与实现
一、前言 随着人口老龄化的加剧,养老院作为老年人养老的重要场所,其管理的高效性和科学性显得尤为重要。传统的养老院管理方式多依赖人工操作,存在信息记录不及时、不准确,管理流程繁琐,资源调配困难等问题。利用信息技…...
Ansible变量详解(变量定义+变量优先级+变量注册+层级定义变量+facts缓存变量)
本篇文章详细给大家介绍Ansible变量,变量适合管理剧本中每个项目的动态值,或是某些值在多个地方重复使用,如果将此值设置为变量再在其他地方调用会方便许多。会用变量,才算真正会用Ansible,话不多说,直接开…...
面向对象系统的分析和设计
来源:《设计模式精解-GOF23种设计模式解析》 作者:k_eckel k_eckels mindview - 博客园 (cnblogs.com) --------- 面向对象系统的分析和设计实际上追求的就是两点: (1)高内聚 (2)低耦合 …...
Vue 提供了Transition,可以帮助你制作基于状态变化的过渡和动画
官方文档:https://cn.vuejs.org/guide/built-ins/transition.html Transition Vue 提供了两个内置组件,可以帮助你制作基于状态变化的过渡和动画: <Transition> 会在一个元素或组件进入和离开 DOM 时应用动画。本章节会介绍如何使用…...
视频编辑技术:一键生成混剪视频的AI技术应用
随着视频内容的爆炸式增长,视频编辑技术也在不断进步。本文将探讨如何利用AI技术,实现一键生成混剪视频,并自动添加配音和字幕,以提高视频编辑的效率和质量。 AI技术在视频编辑中的应用 AI技术在视频编辑领域的应用越来越广泛&am…...
Android11 MTK 开机默认启动热点
1、需求:开机后不锁屏,默认打开热点,且长时间没有设备连接热点时保证热点也是打开的。 2、开机后不锁屏: 路径:vendor/mediatek/proprietary/packages/apps/SettingsProvider/res/values/defaults.xml<bool name&q…...
Vue Web开发(二)
1. 项目搭建 1.1. 首页架子搭建 使用Element ui中的Container布局容器,选择倒数第二个样式,将代码复制到Home.vue。 1.1.1.下载less (1)下载less样式 npm i less (2)下载less编辑解析器 npm i less…...
Linux-实用操作
文章目录 一. 各类实用小技巧(快捷键)1. ctrl c 强制停止2. ctrl d 退出登出3. history 查看历史命令4. !命令前缀,自动匹配上一个命令5. ctrl r,搜索历史命令6. ctrl a | e,光标移动到命令开始或结束7. ctrl ← | →,左右跳…...
Elasticsearch:使用 Elastic APM 监控 Android 应用程序
一、前言 人们通过私人和专业的移动应用程序在智能手机上处理越来越多的事情。 拥有成千上万甚至数百万的用户,确保出色的性能和可靠性是移动应用程序和相关后端服务的提供商和运营商面临的主要挑战。 了解移动应用程序的行为、崩溃的发生和类型、响应时间慢的根本…...
Go的简单问题问答
基础问题回答 Go 的主要特点是什么? 简洁:语法简化,减少复杂性。并发:内置 Goroutine 和 Channel,支持轻量级并发。静态类型:强类型语言,编译时检查错误。跨平台:编译生成独立的二进…...
【攻防实验】溯源与取证分析实验
溯源与取证分析实验 溯源取证分析作为网络攻防过程中重要环节,准确找到攻击者的入侵线索(尤其是攻击突破口、攻击IP地址、域名、工具等信息),对于企业或者团队安全运营团队来说都是必备技能。常规攻击取证过程中往往会结合流量、Web访问日志、终端系统或…...
THREE.js 入门(一)xyz坐标系
一、坐标系概念 在 three.js 中,相机的默认朝向是沿着 Z 轴的负方向。也就是说,默认情况下,相机会沿着 Z 轴的负方向“看”到场景中的对象,而 X 轴和 Y 轴分别对应水平方向和垂直方向。换句话说,相机的默认位置是 (0,…...
AUTOSAR CP中基于通信模块(COM)的Transformer-R24的规范导读
该文档是关于 AUTOSAR CP中基于通信模块(COM)的Transformer的规范说明,主要内容包括引言、相关文档、约束与假设、功能规范、API 规范、配置规范等,旨在为汽车电子系统开发中基于 COM 的Transformer提供全面的技术规范和指导。 一…...
ubuntu20.04安装anygrasp_sdk
ubuntu20.04安装anygrasp_sdk采坑记录 安装ME的教程看上一篇,现在来看anygrasp安装问题grasp_detection、grasp_trackinglicense申请demo文件的运行注意的地方到这以为大功告成了,然后出现了一个numpy版本不匹配问题最后还有一个问题就是修改demo.sh,不然没法可视化结果展示安…...
Spring完整知识点二
Spring注解开发 Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,它能够代替xml配置文件,可以简化配置,提高开发效率Spring注解根据出现时间分类 Spring原始注解…...
GESP三级集训——课堂笔记(部分)
进制转换(二进制、十进制、八进制、十六进制等) 十进制(逢十进一)——Decimal 十进制是我们生活中最常见的进制,如“1”“23”“891”等: 进位过程如下:{1,2,3,4,5,6,7,8,9}{10,11,12,13,14,…...
Spring Boot接口返回统一格式
统一的标准数据格式好处 SpringBoot返回统一的标准数据格式主要有以下几点好处: 增强接口的可读性和可维护性,使得前端开发人员能够更加清晰地理解接口返回的数据结构,从而提高开发效率。 降低前后端耦合度,当后端需要修改返回数…...
Flink如何基于数据版本使用最新离线数据
业务场景 假设批量有一张商户表,表字段中有商户名称和商户分类两个字段。 批量需要将最新的商户名称和分类的映射关系推到hbase供实时使用。 原实现方案 a.原方案内容 为解决批量晚批问题,批量推送hbase表时一份数据产生两类rowkey:T-1和…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
【Linux】自动化构建-Make/Makefile
前言 上文我们讲到了Linux中的编译器gcc/g 【Linux】编译器gcc/g及其库的详细介绍-CSDN博客 本来我们将一个对于编译来说很重要的工具:make/makfile 1.背景 在一个工程中源文件不计其数,其按类型、功能、模块分别放在若干个目录中,mak…...
学习一下用鸿蒙DevEco Studio HarmonyOS5实现百度地图
在鸿蒙(HarmonyOS5)中集成百度地图,可以通过以下步骤和技术方案实现。结合鸿蒙的分布式能力和百度地图的API,可以构建跨设备的定位、导航和地图展示功能。 1. 鸿蒙环境准备 开发工具:下载安装 De…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
网页端 js 读取发票里的二维码信息(图片和PDF格式)
起因 为了实现在报销流程中,发票不能重用的限制,发票上传后,希望能读出发票号,并记录发票号已用,下次不再可用于报销。 基于上面的需求,研究了OCR 的方式和读PDF的方式,实际是可行的ÿ…...
【Zephyr 系列 16】构建 BLE + LoRa 协同通信系统:网关转发与混合调度实战
🧠关键词:Zephyr、BLE、LoRa、混合通信、事件驱动、网关中继、低功耗调度 📌面向读者:希望将 BLE 和 LoRa 结合应用于资产追踪、环境监测、远程数据采集等场景的开发者 📊篇幅预计:5300+ 字 🧭 背景与需求 在许多 IoT 项目中,单一通信方式往往难以兼顾近场数据采集…...
深入理解 C++ 左值右值、std::move 与函数重载中的参数传递
在 C 编程中,左值和右值的概念以及std::move的使用,常常让开发者感到困惑。特别是在函数重载场景下,如何合理利用这些特性来优化代码性能、确保语义正确,更是一个值得深入探讨的话题。 在开始之前,先提出几个问题&…...
