工具类-列表请求工具 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和…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...

苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...