Vue3 后台分页写腻了?我用 1 个 Hook 删掉 90% 重复代码(附源码)
?还在为每个列表页写重复的分页代码而烦恼吗? 还在复制粘贴 currentPage、pageSize、loading 等状态吗? 一个 Hook 帮你解决所有分页痛点,减少90%重复代码
?背景与痛点在后台管理系统开发中,分页列表查询非常常见,我们通常需要处理:
当前页、页大小、总数等分页状态加载中、错误处理等请求状态搜索、刷新、翻页等分页操作数据缓存和重复请求处理这些重复逻辑分散在各个组件中,维护起来很麻烦。
为了解决这个烦恼,我专门封装了分页数据管理 Hook。现在只需要几行代码,就能轻松实现分页查询,省时又高效,减少了大量重复劳动
使用前提 - 接口格式约定查询接口返回的数据格式:
{
list: [ // 当前页数据数组
{id:1,name:'user1'},
{id:2,name:'user2'}
],
total:100 // 数据总条数
}
先看效果:分页查询只需几行代码!importusePageFetchfrom'@/hooks/usePageFetch'// 引入分页查询 Hook,封装了分页逻辑和状态管理
import{ getUserList }from'@/api/user' // 引入请求用户列表的 API 方法
// 使用 usePageFetch Hook 实现分页数据管理
const{
currentPage, // 当前页码
pageSize, // 每页条数
total, // 数据总数
data, // 当前页数据列表
isFetching, // 加载状态,用于控制 loading 效果
search, // 搜索方法
onSizeChange, // 页大小改变事件处理方法
onCurrentChange // 页码改变事件处理方法
} = usePageFetch(
getUserList, // 查询API
{initFetch:false} // 是否自动请求一次(组件挂载时自动拉取第一页数据)
)
这样子每次分页查询只需要引入hook,然后传入查询接口就好了,减少了大量重复劳动
解决方案我设计了两个相互配合的 Hook:
「useFetch」:基础请求封装,处理请求状态和缓存「usePageFetch」:分页逻辑封装,专门处理分页相关的状态和操作usePageFetch (分页业务层)
├── 管理 page / pageSize / total 状态
├── 处理搜索、刷新、翻页逻辑
├── 统一错误处理和用户提示
└── 调用 useFetch (请求基础层)
├── 管理 loading / data / error 状态
├── 可选缓存机制(避免重复请求)
└── 成功回调适配不同接口格式
核心实现useFetch - 基础请求封装// hooks/useFetch.js
import{ ref }from'vue'
constCache =newMap()
/**
* 基础请求 Hook
*@param{Function}fn- 请求函数
*@param{Object}options- 配置选项
*@param{*}options.initValue - 初始值
*@param{string|Function}options.cache - 缓存配置
*@param{Function}options.onSuccess - 成功回调
*/
functionuseFetch(fn, options = {}){
constisFetching = ref(false)
constdata = ref()
consterror = ref()
// 设置初始值
if(options.initValue !==undefined) {
data.value = options.initValue
}
functionfetch(...args){
isFetching.value =true
letpromise
if(options.cache) {
constcacheKey =typeofoptions.cache ==='function'
? options.cache(...args)
: options.cache ||`${fn.name}_${args.join('_')}`
promise = Cache.get(cacheKey) || fn(...args)
Cache.set(cacheKey, promise)
}else{
promise = fn(...args)
}
// 成功回调处理
if(options.onSuccess) {
promise = promise.then(options.onSuccess)
}
returnpromise
.then(res={
data.value = res
isFetching.value =false
error.value =undefined
returnres
})
.catch(err={
isFetching.value =false
error.value = err
returnPromise.reject(err)
})
}
return{
fetch,
isFetching,
data,
error
}
}
exportdefaultuseFetch
usePageFetch - 分页逻辑封装// hooks/usePageFetch.js
import{ ref, onMounted, toRaw, watch }from'vue'
importuseFetchfrom'./useFetch'// 即上面的hook --- useFetch
import{ ElMessage }from'element-plus'
/**
* 分页数据管理 Hook
*@param{Function}fn- 请求函数
*@param{Object}options- 配置选项
*@param{Object}options.params - 默认参数
*@param{boolean}options.initFetch - 是否自动初始化请求
*@param{Ref}options.formRef - 表单引用
*/
functionusePageFetch(fn, options = {}){
// 分页状态
constpage = ref(1)
constpageSize = ref(10)
consttotal = ref(0)
constdata = ref([])
constparams = ref()
constpendingCount = ref(0)
// 初始化参数
params.value = options.params
// 使用基础请求 Hook
const{ isFetching,fetch: fetchFn, error,data: originalData } = useFetch(fn)
// 核心请求方法
constfetch =async(searchParams, pageNo, size) = {
try{
// 更新分页状态
page.value = pageNo
pageSize.value = size
params.value = searchParams
// 发起请求
awaitfetchFn({
page: pageNo,
pageSize: size,
// 使用 toRaw 避免响应式对象问题
...(searchParams ? toRaw(searchParams) : {})
})
// 处理响应数据
data.value = originalData.value?.list || []
total.value = originalData.value?.total ||0
pendingCount.value = originalData.value?.pendingCounts ||0
}catch(e) {
console.error('usePageFetch error:', e)
ElMessage.error(e?.msg || e?.message ||'请求出错')
// 清空数据,提供更好的用户体验
data.value = []
total.value =0
}
}
// 搜索 - 重置到第一页
constsearch =async(searchParams) = {
awaitfetch(searchParams,1, pageSize.value)
}
// 刷新当前页
constrefresh =async() = {
awaitfetch(params.value, page.value, pageSize.value)
}
// 改变页大小
constonSizeChange =async(size) = {
awaitfetch(params.value,1, size)// 重置到第一页
}
// 切换页码
constonCurrentChange =async(pageNo) = {
awaitfetch(params.value, pageNo, pageSize.value)
}
// 组件挂载时自动请求
onMounted(()={
if(options.initFetch !==false) {
search(params.value)
}
})
// 监听表单引用变化(可选功能)
watch(
()=options.formRef,
(formRef) = {
if(formRef) {
console.log('Form ref updated:', formRef)
}
}
)
return{
// 分页状态
currentPage: page,
pageSize,
total,
pendingCount,
// 数据状态
data,
originalData,
isFetching,
error,
// 操作方法
search,
refresh,
onSizeChange,
onCurrentChange
}
}
exportdefaultusePageFetch
完整使用示例用element ui举例template
el-form:model="searchForm"
el-form-itemlabel="用户名"
el-inputv-model="searchForm.username"/
/el-form-item
el-form-item
el-buttontype="primary"@click="handleSearch"搜索/el-button
/el-form-item
/el-form
!-- 表格数据展示,绑定 data 和 loading 状态 --
el-table:data="data"v-loading="isFetching"
!-- ...表格列定义... --
/el-table
!-- 分页组件,绑定当前页、页大小、总数,并响应切换事件 --
el-pagination
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:total="total"
@size-change="onSizeChange"
@current-change="onCurrentChange"
/
/template
script setup
import { ref } from 'vue'
import usePageFetch from '@/hooks/usePageFetch' // 引入分页查询 Hook,封装了分页逻辑和状态管理
import { getUserList } from '@/api/user' // 引入请求用户列表的 API 方法
// 搜索表单数据,响应式声明
const searchForm = ref({
username: ''
})
// 使用 usePageFetch Hook 实现分页数据管理
const {
currentPage, // 当前页码
pageSize, // 每页条数
total, // 数据总数
data, // 当前页数据列表
isFetching, // 加载状态,用于控制 loading 效果
search, // 搜索方法
onSizeChange, // 页大小改变事件处理方法
onCurrentChange // 页码改变事件处理方法
} = usePageFetch(
getUserList,
{ initFetch: false } // 是否自动请求一次(组件挂载时自动拉取第一页数据)
)
/**
* 处理搜索操作
*/
const handleSearch = () = {
search({ username: searchForm.value.username })
}
/script
高级用法带缓存const{
data,
isFetching,
search
} = usePageFetch(getUserList, {
cache:(params) =`user-list-${JSON.stringify(params)}`// 自定义缓存 key
})
设计思路解析「职责分离」:useFetch 专注请求状态管理,usePageFetch 专注分页逻辑「统一错误处理」:在 usePageFetch 层统一处理错误「智能缓存机制」:支持多种缓存策略「生命周期集成」:自动在组件挂载时请求数据总结这套分页管理 Hook 的优势:
开发效率高,减少90%的重复代码,新增列表页从 30 分钟缩短到 5 分钟状态管理完善,自动处理加载、错误、数据状态缓存机制,避免重复请求错误处理统一,用户体验一致易于扩展,支持自定义配置和回调AI编程资讯AI Coding专区指南:
https://aicoding.juejin.cn/aicoding
点击"阅读原文"了解详情~
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线