useList 通用列表管理hook 在开发列表页面时,重复的分页、筛选、加载状态管理往往耗费大量精力。每次都需要手动处理 loading 状态、维护分页参数、决定数据追加或替换逻辑。为解决这些问题,useList 通用列表管理 hook 应运而生,专门统一管理列表相关逻辑。 核心功能 该 Vue
useList通用列表管理Hook
作者:技术拾光
时间:2026-05-28
浏览:299
文章目录
useList 通用列表管理hook
在开发列表页面时,重复的分页、筛选、加载状态管理往往耗费大量精力。每次都需要手动处理 loading 状态、维护分页参数、决定数据追加或替换逻辑。为解决这些问题,useList 通用列表管理 hook 应运而生,专门统一管理列表相关逻辑。

核心功能
该 Vue 自定义 hook 能够显著减少重复代码,主要功能包括:
- 响应式状态管理:使用
reactive封装筛选条件和分页配置,修改一处即可联动全局,无需手动同步。 - 数据请求封装:集成 loading 状态、错误捕获、数据赋值等流程,无需在组件中重复编写 try catch 逻辑。
- 追加/替换模式:通过
isPush参数,既支持普通分页翻页替换,也适用于滚动加载持续追加场景。 - 筛选重置:保存初始筛选条件,支持一键恢复初始状态,优化用户体验。
详细代码
以下为完整代码实现,关键部分已添加注释:
import { reactive, ref, toRaw } from 'vue'export interface IPage {
pageNum: number
pageSize: number
total: number
}export interface GetListFnArgsType {
pagination: IPage
filter: T
}
export default function useListextends Record<string, any>>(
filterOption: U,
getListFn: (args: GetListFnArgsType) => Promise<InResult<{ records: Array, total: number }>>, // InResult 类型定义请阅读uni.request 二次封装这篇文章
pageOption: IPage,
) {
const list = ref<Array>([])
const loading = ref(false)
const filter = reactive({ ...filterOption })
const pagination = reactive<IPage>({
...pageOption,
pageSize: pageOption.pageSize || 10,
pageNum: pageOption.pageNum || 1,
}) // 请求列表数据
const loadData = async (curPage = pagination.pageNum, isPush = true) => {
pagination.pageNum = curPage
loading.value = true const req = { pagination, filter: toRaw(filter) as U }
try {
const res = await getListFn(req as GetListFnArgsType)
loading.value = false
if (!isPush) {
list.value = res.data.records as Array<any>
}
else {
list.value = [...list.value, ...res.data.records as Array<any>]
}
pagination.total = res.data.total
}
catch (error) {
loading.value = false
console.log(error)
}
} // 重置筛选
const filterReset = () => {
Object.assign(filter, filterOption)
loadData(1, false)
} // 搜索
const filterSearch = () => {
console.log(121212)
return loadData(1, false)
} // 分页请求
const loadDataPage = (page: number, isPush = true) => {
return loadData(page, isPush)
} // 分页大小改变
const loadDataPageSize = (pageSize: number) => {
pagination.pageSize = pageSize
loadData(1, false)
} return {
list,
loading,
filter,
pagination,
loadData,
loadDataPage,
loadDataPageSize,
filterReset,
filterSearch,
}
}
类型定义
IPage
分页参数的基础接口,包含三个常规属性:
interface IPage {
pageNum: number // 当前页码
pageSize: number // 每页条数
total: number // 总条数(响应数据填充)
}
GetListFnArgsType
请求函数接收的参数类型,将分页和筛选整合在一起:
interface GetListFnArgsType {
pagination: IPage // 分页信息
filter: T // 筛选条件
}
返回值
调用该 hook 后,可获取以下属性和方法:
| 属性 | 类型 | 说明 |
|---|---|---|
list | Ref | 列表数据 |
loading | Ref | 加载状态 |
filter | Reactive | 筛选条件(响应式) |
pagination | Reactive | 分页信息(响应式) |
loadData | (curPage?: number, isPush?: boolean) => Promise | 加载数据 |
loadDataPage | (page: number, isPush?: boolean) => Promise | 分页加载 |
loadDataPageSize | (pageSize: number) => void | 改变每页条数 |
filterReset | () => void | 重置筛选条件 |
filterSearch | () => Promise | 搜索(重置页码为1) |
方法详解
loadData(curPage, isPush)
核心加载方法,需传入目标页码及是否追加数据:
- curPage:默认值为当前分页页码,通常无需传入。
- isPush:
true表示将新数据追加到现有列表末尾(适用于滚动加载),false则直接替换列表数据(适用于常规翻页)。
loadDataPage(page, isPush)
直接调用 loadData 方法的封装,方便在模板中绑定事件处理。
loadDataPageSize(pageSize)
当用户切换每页条数时,更新分页配置并立即请求第一页数据。
filterReset()
将筛选条件重置为初始值,并重新加载第一页数据。采用覆盖式重置而非合并。
filterSearch()
搜索按钮触发时,将页码重置为 1 后重新请求数据,确保筛选条件变更后结果从第一页开始展示。
使用示例
以下为实际项目案例,管理一个"事件列表",筛选条件包含关键词、状态、起止时间:
import type { IEvent } from '@/api/task/type'
import { getEventList } from '@/api/task'
import useList, { type GetListFnArgsType } from '@/hooks/uesList.hook'const filterOption = {
keyword: '',
status: '',
startTime: '',
endTime: '',
}async function getListData(params: GetListFnArgsType<typeof filterOption>) {
const { pagination, filter } = params
const req = {
params: {
pageNo: pagination.pageNum,
pageSize: pagination.pageSize,
},
data: {
startTime: filter.startTime,
endTime: filter.endTime,
},
} const res = await getEventList(req)
return res /**
* 这里假设接口返回的数据格式不是 records 和 total
* 你可以在 getListData 中根据实际情况调整 records 和 total 的赋值,如:
* const res = await getEventList(req);
* return {data: { records: res.data.list, total: res.data.totalSize }}
*/
}const { list, filter, pagination, filterSearch, loadDataPage } = useList<IEvent, typeof filterOption>(filterOption, getListData, { pageNum: 1, pageSize: 20 })
在模板中,可配合自定义列表组件 cus-list 使用,将筛选条件绑定到 filter,分页事件绑定到 loadDataPage:
<template>
<cus-list v-model:keyword="filter.keyword" :refresh-func="filterSearch" :load-func="loadDataPage" :pagination="pagination" @search="filterSearch" @clear="filterSearch">
<template #serach-left>
<van-dropdown-menu style="--dropdown-menu-title-active-text-color: var(--uni-color-primary);--dropdown-menu-background-color: transparent;--dropdown-menu-title-text-color: var(--uni-text-color); --dropdown-menu-option-active-color: var(--uni-color-primary)" custom-class="min-w-[100rpx]">
<van-dropdown-item v-model:value="filter.siteId" :options="siteIdOptions" @change="onSiteChange" />
van-dropdown-menu>
template> <template #search-filter="{ data }">
<view class="box-border h-full w-full p-2">
<view class="flex items-center gap-2">
<view>开始时间:view>
<cus-date-picker v-model="filter.startTime" type="date" />
view>
<view class="mt-2 flex items-center gap-2">
<view>结束时间:view>
<cus-date-picker v-model="filter.endTime" type="date" />
view>
<view class="mt-5 flex gap-2">
<van-button size="small" block class="flex-1" @click="onFilterReset(data)">
重置
van-button>
<van-button type="primary" block size="small" class="flex-1" @click="onFilter(data)">
确定
van-button>
view>
view>
template> <template #default>
<task-item v-for="item in list" :key="item.id" :record="item" />
template>
cus-list>
template>
整个链路中,数据绑定和事件处理均与 hook 对齐,代码量显著减少,且逻辑集中维护,便于后期修改。
实用工具推荐
电子包浆
使用 ›绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
图片压缩
使用 ›选择画质和输出格式,预览压缩前后的大小。
相关文章