Boost Vue App Efficiency: A Reusable Pagination Hook Reduces 90% Repetitive Code
Learn how to eliminate repetitive pagination logic in Vue-based admin panels by using a custom usePageFetch hook that abstracts page state, loading, error handling, and caching, enabling developers to implement paginated data fetching with just a few lines of code and significantly improve development speed.
还在为每个列表页写重复的分页代码而烦恼吗? 还在复制粘贴 currentPage、pageSize、loading 等状态吗? 一个 Hook 帮你解决所有分页痛点,减少90%重复代码
Background and Pain Points
In backend admin system development, pagination list queries are common, requiring handling of current page, page size, total count, loading and error states, search, refresh, page change, data caching and duplicate request handling. These repeated logics are scattered across components, making maintenance cumbersome.
To solve this, a pagination data management Hook is encapsulated, allowing pagination queries with only a few lines of code, saving time and reducing repetitive work.
Prerequisite – API Response Format
The query API should return data in the following format:
{
"list": [
// current page data array
{ "id": 1, "name": "user1" },
{ "id": 2, "name": "user2" }
],
"total": 100 // total number of items
}Demo: Pagination Query in a Few Lines
import usePageFetch from '@/hooks/usePageFetch' // pagination Hook
import { getUserList } from '@/api/user' // API method
const {
currentPage,
pageSize,
total,
data,
isFetching,
search,
onSizeChange,
onCurrentChange
} = usePageFetch(getUserList, { initFetch: false })This way each pagination query only needs to import the hook and pass the request function, eliminating a lot of repetitive code.
Solution
Two cooperating Hooks are designed:
useFetch : basic request wrapper handling request state and caching.
usePageFetch : pagination logic wrapper managing pagination‑related state and operations.
usePageFetch (pagination layer)
├── Manage page / pageSize / total state
├── Handle search, refresh, page change logic
├── Unified error handling and user messages
└── Calls useFetch (request layer)
├── Manage loading / data / error state
├── Optional caching to avoid duplicate requests
└── Success callback adaptation for different API formatsCore Implementation – useFetch
// hooks/useFetch.js
import { ref } from 'vue'
const Cache = new Map()
/**
* Basic request Hook
* @param {Function} fn - request function
* @param {Object} options
* @param {*} options.initValue - initial value
* @param {string|Function} options.cache - cache config
* @param {Function} options.onSuccess - success callback
*/
function useFetch(fn, options = {}) {
const isFetching = ref(false)
const data = ref()
const error = ref()
if (options.initValue !== undefined) {
data.value = options.initValue
}
function fetch(...args) {
isFetching.value = true
let promise
if (options.cache) {
const cacheKey = typeof options.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)
}
return promise
.then(res => {
data.value = res
isFetching.value = false
error.value = undefined
return res
})
.catch(err => {
isFetching.value = false
error.value = err
return Promise.reject(err)
})
}
return { fetch, isFetching, data, error }
}
export default useFetchCore Implementation – usePageFetch
// hooks/usePageFetch.js
import { ref, onMounted, toRaw, watch } from 'vue'
import useFetch from './useFetch'
import { ElMessage } from 'element-plus'
function usePageFetch(fn, options = {}) {
const page = ref(1)
const pageSize = ref(10)
const total = ref(0)
const data = ref([])
const params = ref()
const pendingCount = ref(0)
params.value = options.params
const { isFetching, fetch: fetchFn, error, data: originalData } = useFetch(fn)
const fetch = async (searchParams, pageNo, size) => {
try {
page.value = pageNo
pageSize.value = size
params.value = searchParams
await fetchFn({
page: pageNo,
pageSize: size,
...(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 || 'Request error')
data.value = []
total.value = 0
}
}
const search = async (searchParams) => {
await fetch(searchParams, 1, pageSize.value)
}
const refresh = async () => {
await fetch(params.value, page.value, pageSize.value)
}
const onSizeChange = async (size) => {
await fetch(params.value, 1, size)
}
const onCurrentChange = async (pageNo) => {
await fetch(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
}
}
export default usePageFetchFull Usage Example (Element UI)
<template>
<el-form :model="searchForm">
<el-form-item label="Username">
<el-input v-model="searchForm.username" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">Search</el-button>
</el-form-item>
</el-form>
<el-table :data="data" v-loading="isFetching">
<!-- table columns -->
</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'
import { getUserList } from '@/api/user'
const searchForm = ref({ username: '' })
const {
currentPage,
pageSize,
total,
data,
isFetching,
search,
onSizeChange,
onCurrentChange
} = usePageFetch(getUserList, { initFetch: false })
const handleSearch = () => {
search({ username: searchForm.value.username })
}
</script>Advanced Usage – With Caching
const { data, isFetching, search } = usePageFetch(getUserList, {
cache: (params) => `user-list-${JSON.stringify(params)}`
})Design Rationale
Separation of Concerns : useFetch focuses on request state, usePageFetch on pagination logic.
Unified Error Handling : errors are handled centrally in usePageFetch.
Smart Caching : supports multiple caching strategies.
Lifecycle Integration : automatically fetches data on component mount.
Conclusion
The pagination management Hook boosts development efficiency, cutting repetitive code by up to 90%, provides comprehensive state management, caching, unified error handling, and is easy to extend with custom configurations.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
