實戰推薦:
還在為每個列表頁寫重複的分頁代碼而煩惱嗎?還在複製貼上 currentPage、pageSize、loading 等狀態嗎?一個 Hook 幫你解決所有分頁痛點,減少 90% 重複代碼。
在後台管理系統開發中,分頁列表查詢非常常見,我們通常需要處理:
這些重複邏輯分散在各個元件中,維護起來很麻煩。
為了解決這個煩惱,我專門封裝了分頁資料管理 Hook。現在只需要幾行代碼,就能輕鬆實現分頁查詢,省時又高效,減少了大量重複勞動。
查詢接口返回的資料格式:
{
list: [ // 當前頁數據數組
{ id: 1, name: 'user1' },
{ id: 2, name: 'user2' }
],
total: 100 // 資料總條數
}
import usePageFetch from '@/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:
usePageFetch (分頁業務層)
├── 管理 page / pageSize / total 狀態
├── 處理搜尋、刷新、翻頁邏輯
├── 統一錯誤處理和用戶提示
└── 呼叫 useFetch (請求基礎層)
├── 管理 loading / data / error 狀態
├── 可選快取機制(避免重複請求)
└── 成功回調適配不同接口格式
// hooks/useFetch.js
import { ref } from 'vue'
const Cache = new Map()
/**
* 基礎請求 Hook
* @param {Function} fn - 請求函數
* @param {Object} options - 配置選項
* @param {*} options.initValue - 初始值
* @param {string|Function} options.cache - 快取配置
* @param {Function} options.onSuccess - 成功回調
*/
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 useFetch
// hooks/usePageFetch.js
import { ref, onMounted, toRaw, watch } from 'vue'
import useFetch from './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 - 表單引用
*/
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
// 使用基礎請求 Hook
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,
// 使用 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
}
}
// 搜尋 - 重置到第一頁
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 usePageFetch
<template>
<el-form :model="searchForm" >
<el-form-item label="用戶名">
<el-input v-model="searchForm.username" />
</el-form-item>
<el-form-item>
<el-button type="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
})
這套分頁管理 Hook 的優勢:
如果覺得對您有幫助,歡迎按讚 👍 收藏 ⭐ 關注 🔔 支持一下!