Vue3 后台分页写腻了?我用 1 个 Hook 删掉 90% 重复代码

还在为每个页面写重复的增删查改代码而烦恼吗? 还在复制粘贴 currentPage、pageSize、loading 等状态吗? 一个 Hook 帮你解决所有分页痛点,减少90%重复代码

背景与痛点

在后台管理系统开发中,分页列表查询非常常见,我们通常需要处理:

  • 当前页、页大小、总数等分页状态
  • 加载中、错误处理等请求状态
  • 搜索、刷新、翻页等分页操作
  • 数据缓存和重复请求处理

这些重复逻辑分散在各个组件中,维护起来很麻烦。

为了解决这个烦恼,我专门封装了分页数据管理 Hook。现在只需要几行代码,就能轻松实现分页查询,省时又高效,减少了大量重复劳动

使用前提 - 接口格式约定

查询接口返回的数据格式:

js 复制代码
{
  list: [        // 当前页数据数组
    { id: 1, name: 'user1' },
    { id: 2, name: 'user2' }
  ],
  total: 100     // 数据总条数
}

这里一般都是这样定义的。除了这些主要定义增删查改接口API的 url、method的约定 将url分成几部:前缀(prefix)、页面名字(pageName)、后缀(suffix),后缀和method是可以约定好的,我们就可以封装到hooks中。

  • 条件筛选 suffix:'pageList', method: 'get'

  • 新增 suffix:'add', method: 'post'

  • 删除 suffix:'delete', method: 'delete'

  • 修改 suffix:'update', method: 'put'

  • 详情查看 suffix:'get', method: 'get'

这些约定好就不用每次都写了

先看效果:一个页面就一百行代码

js 复制代码
<template>
  <Page>
    <SPageSearch :formConfig="searchConfig"> </SPageSearch>
    <div class="py-[13px] px-[15px]">
      <SButtonGroup @add="open('add')"></SButtonGroup>
    </div>
    <SPageContent :contentConfig="pageConfig">
      <template #action="{ row }">
        <div class="flex justify-center">
          <SButtonGroup size="small" @edit="open('edit', row.id)" @del="deleteRow(row.id)" :order="['edit', 'del']" />
        </div>
      </template>
    </SPageContent>
    <SPageDialog width="40vw" :formConfig="dialogFormConfig"> </SPageDialog>
  </Page>
</template>

<script setup lang="ts">
const searchConfig = $defineFormConfig(() => {
  return {
    formItems: [
      {
        field: 'customer',
        label: $t('basicInformation.customer'),
        type: 'input',
        config: {
          placeholder: $t('basicInformation.customerPlaceholder'),
        },
      },
    ],
  }
})

const { deleteRow, open } = $usePageHook(() => {
  return {
    title: $t('basicInformation.customerInformation'),
    pageName: 'customer',
    prefix: 'basic',
  }
})

const pageConfig = $defineSTableConfig(() => {
  return {
    columns: [
      { type: 'seq', width: 70 },
      { field: 'customerName', title: $t('basicInformation.customerName') },
      { field: 'customerCode', title: $t('basicInformation.customerCode') },
      { field: 'remark', title: $t('basicInformation.describe') },
      { field: 'createUserName', title: $t('basicInformation.creator') },
      { field: 'createTime', title: $t('basicInformation.creationTime') },
      {
        field: 'action',
        title: $t('common.operate'),
        width: 200,
        slots: { default: 'action' },
        fixed: 'right',
      },
    ],
  }
})

const dialogFormConfig = $defineFormConfig(() => {
  return {
    colLayout: { span: 24 },
    formItems: [
      {
        field: 'customerName',
        label: $t('basicInformation.customerName'),
        type: 'input',
        formItemConfig: {
          rules: [{ required: true, message: $t('basicInformation.customerName') + $t('common.rulehHint'), trigger: 'change' }],
        },
      },
      {
        field: 'customerCode',
        label: $t('basicInformation.customerCode'),
        type: 'input',
        formItemConfig: {
          rules: [{ required: true, message: $t('basicInformation.customerCode') + $t('common.rulehHint'), trigger: 'change' }],
        },
      },
      {
        field: 'remark',
        label: $t('basicInformation.describe'),
        type: 'textarea',
        layout: { span: 24 },
      },
    ],
  }
})
</script>
<style scoped lang="scss"></style>

使用hooks

kotlin 复制代码
const { deleteRow, open } = $usePageHook(() => {
  return {
    title: $t('basicInformation.customerInformation'),
    pageName: 'customer',
    prefix: 'basic',
  }
})

这么几行代码完成了这个页面的所有的业务逻辑,在hook中使用注入的方式,再配合封装的组件,爽歪歪!

核心设计思想

1. 配置驱动设计

typescript

css 复制代码
// 通过配置对象定义页面行为
const pageConfig = {
  search: { /* 搜索配置 */ },
  add: { /* 新增配置 */ },
  edit: { /* 编辑配置 */ },
  delete: { /* 删除配置 */ }
}

2. 约定优于配置

typescript

arduino 复制代码
// 默认的 URL 路径约定
const defaultSuffix = {
  search: 'pageList',
  add: 'add', 
  edit: 'update',
  delete: 'delete',
  look: 'get'
}

架构分层

第一层:数据状态管理

typescript

csharp 复制代码
// 核心响应式数据
const searchFormData = ref({})        // 搜索表单数据
const tableData = ref([])             // 表格数据  
const formDialogData = ref({})        // 弹窗表单数据
const pagerConfig = reactive({})      // 分页配置
const dialogVisible = ref(false)      // 弹窗显示状态
const loading = ref(false)            // 加载状态

第二层:业务操作封装

typescript

scss 复制代码
// 封装所有 CRUD 操作
const operations = {
  search(),    // 搜索
  add(),       // 新增
  edit(),      // 编辑  
  deleteRow(), // 删除
  look(),      // 查看详情
  download()   // 导出
}

第三层:生命周期和依赖注入

typescript

scss 复制代码
// 提供依赖给子组件
provide('searchFormProvide', searchFormProvide)
provide('contentProvide', contentProvide) 
provide('dialogProvide', dialogProvide)

关键技术实现

1. 智能的 Loading 管理

typescript

javascript 复制代码
// 支持多种 loading 控制方式
const loadingProxy = new Proxy(() => {}, {
  get: () => loadingCom.value,           // loading.value 获取值
  set: () => loadingInner.value = newValue, // loading.value = 1 设置值  
  apply: () => loadingProxy(1)           // loading(1) 函数调用
})

// 计数式 loading,避免并发请求问题
function startLoading() { loadingNum.value += 1 }
function endLoading() { loadingNum.value -= 1 }

2. 灵活的请求封装

typescript

csharp 复制代码
// 统一的请求创建函数
function creakSearchRequest(params, dataIn) {
  return async (paramsData) => {
    return await request[method](url, paramsData)
  }
}

// 支持自定义 API 和默认 URL 生成

3. 钩子函数扩展点

typescript

javascript 复制代码
// 前置处理钩子
before: (params) => {
  // 可以修改请求参数
  return modifiedParams
}

// 后置处理钩子  
after: (res) => {
  // 可以处理响应数据
  return processedData
}

4. 类型安全的泛型设计

typescript

php 复制代码
interface PageHookData {
  prefix?: string
  pageName?: string
  search?: unitType & {
    isValidate?: boolean
    list?: string
    total?: string
  }
  // ... 其他操作配置
}

使用方式

基本使用

typescript

javascript 复制代码
const page = usePageHook(() => ({
  pageName: 'user',
  prefix: 'system',
  search: {
    after: (res) => {
      return res.data.records
    }
  }
}))

复杂场景

typescript

javascript 复制代码
const page = usePageHook(({ searchFormData, tableData }) => ({
  // 自定义初始数据
  searchFormData: { status: 1 },
  tableData: [],
  
  // 操作配置
  search: {
    before: (params) => {
      // 请求前处理
      return [modifiedParams]
    },
    after: (res) => {
      // 响应后处理
      return [res.data.list, res.data.total]
    }
  },
  
  // 树形数据
  tree: {
    getData: async (inputValue) => {
      return await getTreeData(inputValue)
    }
  }
}))

设计优势总结

这套分页管理 Hook 的优势:

1. 高度可配置

  • 支持自定义 URL、方法、参数处理
  • 灵活的钩子函数扩展
  • 多种 loading 控制方式

2. 状态管理清晰

  • 明确的数据流:搜索 → 请求 → 更新表格
  • 统一的 loading 状态管理
  • 弹窗状态与数据分离

3. 组件通信便捷

typescript

arduino 复制代码
// 子组件通过 inject 获取功能
const { search, tableData } = inject('pageProvide')

4. 类型安全

完整的 TypeScript 类型定义,提供良好的开发体验。

5. 复用性强

通过配置即可适配不同的业务页面,减少重复代码。

相关推荐
葡萄城技术团队8 分钟前
SpreadJS ReportSheet 与 DataManager 实现 Token 鉴权:全流程详解与代码解析
前端
勤劳打代码8 分钟前
触类旁通 —— Flutter 与 React 对比解析
前端·flutter·react native
Mintopia8 分钟前
🧠 可解释性AIGC:Web场景下模型决策透明化的技术路径
前端·javascript·aigc
Mintopia12 分钟前
⚙️ Next.js 事务与批量操作:让异步的世界井然有序
前端·javascript·全栈
若梦plus14 分钟前
多端开发之React-Native原理浅析
前端·react native
新兵蛋子016 分钟前
基于 vue3 完成领域模型架构建设
前端
今禾19 分钟前
Git完全指南(中篇):GitHub团队协作实战
前端·git·github
Tech_Lin19 分钟前
前端工作实战:如何在vite中配置代理解决跨域问题
前端·后端