前言
在后台管理系统中,表格是最常见的组件之一。一个好的表格 Hook 需要考虑分页、搜索、刷新、数据格式化、状态持久化等方方面面。
最近我分析了一个非常完善的 Vue3 表格 Hook——useTable,它用 200 行代码实现了企业级表格的绝大部分需求。今天就把它的设计精髓分享给大家。
功能概览
useTable 是一个全能型表格数据管理 Hook,提供了以下核心能力:
| 分页管理 | 自动处理 pageNo/pageSize |
| 数据加载 | 支持异步接口请求 |
| 搜索/刷新 | 一键触发数据重载 |
| 数据格式化 | 自定义数据转换逻辑 |
| 状态持久化 | sessionStorage 保存分页状态 |
| 并发请求 | 支持多个接口并行请求 |
| 特性数据 | 支持获取接口返回的额外数据 |
快速开始
import { useTable } from '@/hooks/useTable'
const {
loading,
data,
pagination,
onSearch,
onRefresh,
loadData
} = useTable({
api: fetchList,
query: getSearchParams, // 搜索参数,支持异步
formatter: formatTableData,
hasPagination: true,
init: true,
key: 'user_list', // 用于状态持久化
})
核心设计解析
1. 状态持久化
最让我眼前一亮的是分页状态的 sessionStorage 持久化设计。这解决了用户在列表页刷新后,分页状态丢失的痛点。
// 从存储加载分页状态
const loadPagination = (key: string): Partial<Pagination> => {
if (!key || typeof sessionStorage === 'undefined') return {}
try {
const stored = sessionStorage.getItem(`table_pagination_${key}`)
return stored ? JSON.parse(stored) : {}
} catch (e) {
return {}
}
}
// 保存分页状态
const savePagination = (key: string, pagination: Pagination) => {
if (!key || typeof sessionStorage === 'undefined') return
sessionStorage.setItem(
`table_pagination_${key}`,
JSON.stringify({ pageNo: pagination.pageNo, pageSize: pagination.pageSize })
)
}
设计亮点:
- 使用 sessionStorage 而非 localStorage,页面关闭后自动清除,更符合表格场景
- 异常捕获,保证存储失败不影响主流程
- 支持多表格隔离,通过 key 参数区分
2. 智能分页处理
删除数据时的分页边界处理,往往是容易被忽略的细节:
async function loadData(checkDataLength: boolean | object = false, pageNo?: number) {
// 批量删除时,直接指定页码
if (pageNo) {
state.pagination.pageNo = pageNo
}
// 当列表只剩1条数据且不是第1页时,自动回退到上一页
else if (typeof checkDataLength === 'boolean' && checkDataLength &&
state.data.length === 1 && state.pagination.pageNo > 1) {
state.pagination.pageNo = state.pagination.pageNo – 1
}
// … 发起请求
}
这个 checkDataLength 参数非常巧妙,它让调用者可以控制是否需要这个"智能回退"逻辑。
3. 异步查询参数
支持异步获取查询参数,这是很多 Hook 忽略的点:
const queryBody = query ? await query() : {}
const params = cloneDeep(queryBody)
使用 query 而不是直接传入对象,意味着可以在 query 函数里获取响应式的搜索表单数据:
const searchForm = reactive({ name: '', status: '' })
useTable({
api: fetchList,
query: async () => ({
…searchForm
})
})
4. 数据格式化流水线
Hook 提供了两层格式化,职责分离:
// formatterResponse: 格式化接口返回的原始数据
res.data = formatterResponse ? formatterResponse(res.data) : res.data
// formatter: 格式化列表数据
state.data = formatter ? formatter(list || [], result) : list
这种设计非常灵活:
- formatterResponse 可以处理接口返回结构不标准的情况
- formatter 可以添加序号、状态标签等业务字段
5. 特性数据获取
接口往往不只是返回列表数据,还会返回统计信息等:
// 调用方定义
useTable({
api: fetchList,
queryFeature: (result) => {
// result 包含接口返回的其他字段
summary.value = result.summary
}
})
// Hook 内部
const { list, total, …result } = res?.data || {}
queryFeature && queryFeature(result)
这个设计让表格组件也可以方便地展示汇总数据。
6. 并发请求支持
// 调用方定义
useTable({
api: fetchList,
concurrency: async (params) => {
const [options, categories] = await Promise.all([
fetchOptions(),
fetchCategories()
])
// 处理并发结果
}
})
// Hook 内部
if (concurrency) {
await concurrency(params)
}
这个场景很常见:表格加载时,下拉框选项也需要加载,可以并行请求减少等待时间。
7. 开发环境模拟数据
if (import.meta.env.MODE === 'development') {
defaultData = mockData || []
}
支持在开发阶段传入模拟数据,不用调接口就能预览表格效果。
完整使用示例
<template>
<div>
<!– 搜索表单 –>
<el-form :model="searchForm">
<el-input v-model="searchForm.name" placeholder="请输入姓名" />
<el-button @click="onSearch">搜索</el-button>
</el-form>
<!– 表格 –>
<el-table :data="data" :loading="loading">
<el-table-column prop="name" label="姓名" />
<el-table-column prop="status" label="状态" />
</el-table>
<!– 分页 –>
<el-pagination
v-model:current-page="pagination.pageNo"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
@current-change="loadData()"
/>
</div>
</template>
<script setup>
import { reactive } from 'vue'
import { useTable } from '@/hooks/useTable'
const searchForm = reactive({
name: '',
status: ''
})
const { loading, data, pagination, onSearch, loadData } = useTable({
api: fetchUserList,
query: () => ({ …searchForm }),
key: 'user_management',
formatter: (list) => list.map(item => ({
…item,
statusText: item.status === 1 ? '启用' : '禁用'
})),
queryFeature: (result) => {
console.log('额外数据:', result.extra)
}
})
</script>
优缺点分析
| ✅ 功能全面 | 分页、搜索、刷新、格式化、持久化一应俱全 |
| ✅ 扩展性强 | concurrency、queryFeature 等钩子满足复杂场景 |
| ✅ 体验优化 | 智能分页回退、状态持久化 |
| ⚠️ 类型安全 | 部分地方用了 any,可加强 |
| ⚠️ 依赖外部 | 依赖 lodash-es 和 @vueuse/core |
总结
useTable 是我见过设计最完善的 Vue3 表格 Hook 之一。它的设计思路值得学习:
如果你正在开发后台管理系统,直接抄这份作业就够了。
网硕互联帮助中心


评论前必须登录!
注册