news 2026/9/12 16:28:38

Refine v5 基础表格实战:使用 @refinedev/react-table 与 Chakra UI 构建服务端驱动表格

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Refine v5 基础表格实战:使用 @refinedev/react-table 与 Chakra UI 构建服务端驱动表格

Refine v5 基础表格实战:使用 @refinedev/react-table 与 Chakra UI 构建服务端驱动表格

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

本篇技术指南围绕 Refine v5 官方示例table-chakra-ui-basic(对应文档 basic.md)展开,讲解如何借助@refinedev/react-table适配器,在 Chakra UI 项目中直接使用 TanStack Table(React Table)的全部能力,并让排序、筛选、分页等操作全部落到服务端数据源。读完本文,你将掌握从列定义、服务端排序/筛选、分页组件到关联数据展示的完整实现路径,并理解适配器在 Refine Core 与 React Table 之间的双向同步原理。

示例定位:一张"服务端驱动"的基础表格

Refine 的核心设计之一是"headless 灵活性":它不绑定任何 UI 库,而是通过适配器把数据层能力注入你选定的组件生态。在表格领域,这一角色由@refinedev/react-table承担——它基于 TanStack Table 构建,让开发者可以直接使用 React Table 的全部特性(列配置、排序、筛选、分页、行模型等),同时这些状态变化会被同步到 Refine Core 的useTable,最终反映为对数据提供者的真实请求参数。

table-chakra-ui-basic正是这一能力在 Chakra UI 下的最小完整示例:一张 posts 列表,支持列头排序、列头筛选、分页,并且所有操作都是"服务端模式"(server-side),即数据由 API 按需返回,而非一次性拉全量后在浏览器内存中处理。

该示例的核心依赖(见 package.json):

  • @refinedev/react-table:Refine 与 TanStack Table 的适配层;
  • @tanstack/react-table:底层表格引擎;
  • @refinedev/chakra-ui:提供ListDateFieldShowButtonEditButtonDeleteButtonusePagination等 Refine 组件与 Hook;
  • @chakra-ui/react:UI 组件库;
  • @refinedev/simple-rest:示例数据提供者,请求https://api.fake-rest.refine.dev
  • @refinedev/react-router:路由提供者。

如何运行该示例

npm create refine-app@latest -- --example table-chakra-ui-basic

命令会以官方模板脚手架的方式在本地生成完整项目,随后npm installnpm run dev即可启动。应用入口 main.tsx 与 App.tsx 中可以看到标准装配:ChakraProvider(主题RefineThemes.Blue)包裹<Refine>,注册routerProviderdataProviderposts资源,并开启syncWithLocationwarnWhenUnsavedChanges——前者保证筛选/排序/分页状态可随 URL 同步,后者在离开未保存表单时给出提示。

第一步:用 useTable 把 Refine Core 与 React Table 接通

列表页核心在 list.tsx。它从@refinedev/react-table引入useTable,并解构出两个命名空间:

const { reactTable: { getHeaderGroups, getRowModel, setOptions }, refineCore: { setCurrentPage: setCurrent, pageCount, currentPage: current, tableQuery: { data: tableData }, }, } = useTable({ columns, refineCoreProps: { sorters: { initial: [{ field: "id", order: "desc" }], }, }, });
  • reactTable:TanStack Table 实例,负责列头渲染、行模型、排序/筛选的 UI 状态;
  • refineCore:Refine CoreuseTable的返回值,负责分页、排序、筛选的服务端状态与数据请求。

refineCoreProps会把配置透传给 Core 的useTable。示例中通过sorters.initial设置了默认排序:按id降序。也就是说,页面首次加载时就会带上sorters[0]=id.desc这样的请求参数,配合syncWithLocation,该默认排序还会体现在 URL 中。

在适配器内部(见 packages/react-table/src/useTable/index.ts),useTable先调用 Core 的useTable拿到tableQuerysetCurrentPagesetPageSizesetSorterssetFilterspageCount等,再调用useReactTable构建表格实例,并把服务端模式显式写入:

manualPagination: true, manualSorting: isServerSideSortingEnabled, manualFiltering: isServerSideFilteringEnabled,

其中isServerSideSortingEnabledrefineCoreProps.sorters?.mode(默认"server")决定,筛选同理(默认"server")。当manualSorting/manualFilteringtrue时,TanStack Table 不会在本地排序/过滤数据,而是把状态变化抛给上层,由 Refine 转成 CRUD 请求参数。这正是本示例"服务端驱动"的底层开关。

第二步:列定义(ColumnDef)

列通过React.useMemo包裹的ColumnDef<IPost>[]定义,类型模型见 interfaces/index.d.ts:

export interface IPost { id: number; title: string; content: string; status: "published" | "draft" | "rejected"; category: { id: number }; }

示例共定义 6 列:idtitlestatuscategory.idcreatedAtactions。其中值得注意的配置:

  • accessorKey:直接绑定数据字段;
  • enableColumnFilter: false:该列不出现筛选入口(如idcategory.idcreatedAtactions);
  • enableSorting: false:操作列不可排序;
  • meta:存放 Refine 扩展信息,包括filterOperator与自定义filterElement(见下文筛选部分);
  • cell:自定义单元格渲染,可接收getValuetable等上下文。

第三步:列头排序与筛选组件

列头由getHeaderGroups()渲染,每个Th内除了标题文本,还挂载了自定义的ColumnSorterColumnFilter(见 columnSorter.tsx 与 columnFilter.tsx)。

ColumnSorter:切换排序方向

export const ColumnSorter: React.FC<ColumnButtonProps> = ({ column }) => { if (!column.getCanSort()) return null; const sorted = column.getIsSorted(); return ( <IconButton aria-label="Sort" size="xs" onClick={column.getToggleSortingHandler()} style={{ transform: `rotate(${sorted === "asc" ? "180" : "0"}deg)`, transition: "transform 0.25s", }} variant={sorted ? "light" : "transparent"} icon={!sorted ? <IconSelector size={18} /> : <IconChevronDown size={18} />} /> ); };

逻辑要点:

  • 通过column.getToggleSortingHandler()复用 TanStack Table 的排序切换逻辑(asc → desc → 无);
  • getIsSorted()返回"asc"/"desc"/false,据此旋转图标(升序时箭头向上旋转 180°)并切换按钮配色。

由于适配器开启了manualSorting,点击后排序状态会被同步回 Refine,最终以sorters[]参数请求服务端。

ColumnFilter:弹出式筛选菜单

ColumnFilter使用 Chakra UI 的Menu组件实现点击图标弹出筛选输入框:

  • 默认渲染Input文本输入框;
  • 若列在meta.filterElement中提供了自定义筛选元素,则优先渲染该元素;
  • 提供"清除"(column.setFilterValue(undefined))与"保存"(column.setFilterValue(state.value))两个操作。

list.tsx中,title列通过meta.filterOperator: "contains"指定服务端使用包含匹配;status列则同时指定了filterOperator: "eq"和自定义filterElement——一个包含 published / draft / rejected 三个选项的下拉框:

{ id: "status", header: "Status", accessorKey: "status", meta: { filterElement: ({ value, onChange }) => ( <Select borderRadius="md" size="sm" placeholder="All Status" {...{ value, onChange }}> <option value="published">published</option> <option value="draft">draft</option> <option value="rejected">rejected</option> </Select> ), filterOperator: "eq", }, },

这里的filterOperator会被适配器转换为 CRUD 筛选对象。在 column-filters-to-crud-filters 中,列筛选(columnFilters)会结合列的meta.filterOperator生成形如{ field, operator: "contains" | "eq", value }CrudFilter,最终由数据提供者拼接为?title_like=xxx&status=published之类的查询参数。

第四步:服务端分页与自定义分页组件

分页同样走服务端模式。useTablerefineCore返回currentPagepageCountsetCurrentPage,示例把它们传给自定义的 Pagination:

export const Pagination: FC<PaginationProps> = ({ current, pageCount, setCurrent }) => { const pagination = usePagination({ current, pageCount }); // ... };

usePagination来自@refinedev/chakra-ui,根据当前页与总页数计算出一组可点击的页码条目(含...省略号),返回itemsprevnext。渲染逻辑:

  • pagination.prev存在时渲染"上一页"图标按钮,点击setCurrent(current - 1)
  • 遍历pagination.items,字符串项渲染为...,数字项渲染为页码按钮,当前页用solid变体高亮;
  • pagination.next存在时渲染"下一页"按钮。

点击任意页码都会调用setCurrent,进而触发 Refine 以current(页码)和pageSize参数重新请求数据。在适配器内部,TanStack Table 的pagination.pageIndex变化会通过useEffect回写setCurrentPage(pageIndex + 1)(见 useTable/index.ts),这就是"点击表格自带分页状态也能驱动服务端请求"的双向同步机制。同样,排序或筛选状态变化时,适配器还会自动把页码重置为 1,避免停留在越界页(见 useTable/index.ts)。

第五步:关联数据展示(useMany + table meta)

category.id列只保存了外键 ID,显示时需要联查categories资源。示例使用useMany

const categoryIds = tableData?.data?.map((item) => item.category.id) ?? []; const { result: categoriesData } = useMany<ICategory>({ resource: "categories", ids: categoryIds, queryOptions: { enabled: categoryIds.length > 0 }, });

然后通过setOptionscategoriesData注入表格的meta

setOptions((prev) => ({ ...prev, meta: { ...prev.meta, categoriesData }, }));

单元格渲染时从table.options.meta读取这份数据并查找匹配项:

cell: ({ getValue, table }) => { const meta = table.options.meta as { categoriesData: GetManyResponse<ICategory> }; const category = meta.categoriesData?.data.find((item) => item.id === getValue()); return category?.title ?? "Loading..."; },

这是 TanStack Table 官方的meta扩展点与 Refine 数据 Hook 结合的典型用法:数据通过useMany一次批量获取,避免逐行发请求;查询未返回前显示 "Loading..." 占位。

第六步:操作列与日期字段

actions列使用@refinedev/chakra-ui提供的三个按钮,通过recordItemId绑定当前行 ID,hideText只显示图标、size="sm"紧凑布局:

<HStack> <ShowButton hideText size="sm" recordItemId={getValue() as number} /> <EditButton hideText size="sm" recordItemId={getValue() as number} /> <DeleteButton hideText size="sm" recordItemId={getValue() as number} /> </HStack>

这三个按钮会自动感知资源的路由定义(/posts/show/:id/posts/edit/:id)以及删除确认与通知逻辑。createdAt列则用DateField格式化展示:

cell: ({ getValue }) => <DateField value={getValue() as string} format="LLL" />,

DateField是 Refine 提供的按资源i18nlocale 渲染日期的字段组件,format="LLL"输出类似 "Sep 11, 2026 8:30 PM" 的可读格式。

适配器原理:refineCore 与 reactTable 的双向同步

抛开示例看本质,@refinedev/react-table的核心价值在于四个方向的同步(全部可在 useTable/index.ts 中验证):

  1. 数据流useReactTable({ data: tableQuery.data?.data ?? [] }),服务端返回的当前页数据作为表格数据源;
  2. 分页同步useEffect监听pagination.pageIndex变化调用setCurrentPage(pageIndex + 1)pageSize变化调用setPageSizeCore
  3. 排序同步useEffect监听sorting,将其映射为CrudSortingfield+order)后setSorters,同时重置页码;
  4. 筛选同步useEffect通过 column-filters-to-crud-filters 把columnFilters结合列meta.filterOperator转成CrudFilter[],再叠加 get-removed-filters 计算出的已删除筛选,最终setFilters

反向(从 URL/外部状态恢复表格 UI)则由 crud-filters-to-column-filters 在initialState.columnFilters中完成,配合syncWithLocation,刷新页面后排序与筛选依然保留。这些工具函数都有配套单元测试(如 column-filters-to-crud-filters/index.spec.ts),可作为理解映射规则的最小文档。

另外,packages/react-table导出的适配器同样适用于其他 UI 栈(无 UI 依赖,纯 headless),Chakra UI 侧只负责把状态渲染成组件;本示例的分页复用@refinedev/chakra-uiusePagination,若使用其他 UI 库,可自行实现同等分页逻辑。

验证与测试

仓库为每个示例都配套了 Cypress E2E 测试,本示例对应 cypress/e2e/table-chakra-ui-basic。测试覆盖了关键交互:表格行渲染、列排序点击后的服务端请求、筛选输入后的列表变化、分页跳转等,可作为"示例行为是否符合预期"的可执行验收依据。

小结

通过table-chakra-ui-basic示例可以看到,在 Refine v5 中构建一张"开箱即用"的服务端表格只需三步:

  1. @refinedev/react-tableuseTable声明列与refineCoreProps
  2. 用 Chakra UI 组件渲染getHeaderGroups()getRowModel(),并挂上排序/筛选交互;
  3. refineCore暴露的分页状态配合usePagination渲染分页器。

排序、筛选、分页全部以 CRUD 参数形式作用于数据源,关联数据用useMany+table meta优雅解决,操作列与日期字段直接复用@refinedev/chakra-ui的现成组件。理解适配器在 Core 与 TanStack Table 之间的同步机制后,这套模式可以平滑迁移到 MUI、Mantine、Ant Design 等任意 UI 生态,成为你构建管理后台列表页的通用范式。

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 16:28:18

三条命令给 Windows 11 镜像瘦身 40%:tiny11builder 实操

三条命令给 Windows 11 镜像瘦身 40%&#xff1a;tiny11builder 实操 【免费下载链接】tiny11builder Scripts to build a trimmed-down Windows 11 image. 项目地址: https://gitcode.com/GitHub_Trending/ti/tiny11builder 一块新硬盘装完 Windows 11 官方镜像&#x…

作者头像 李华
网站建设 2026/9/12 16:27:36

TEI推理工具包:工业级NLP任务的高效解决方案

1. TEI Inference Toolkit项目概述TEI Inference Toolkit是一套专为工业级文本处理设计的开源工具包&#xff0c;主要解决Embedding生成、自然语言推理(NLI)和结果重排序(Reranking)三大核心任务。我在实际部署中发现&#xff0c;这套工具特别适合需要处理海量文本同时又对响应…

作者头像 李华
网站建设 2026/9/12 16:24:52

2023年AI论文写作工具测评与使用指南

1. 论文写作工具市场现状分析2023年AI写作工具市场规模已达47亿美元&#xff0c;年增长率超过300%。作为从业多年的学术编辑&#xff0c;我见证了这个领域从简单的语法检查工具发展到如今能辅助完成80%论文写作流程的智能系统。专科生毕业论文写作存在几个典型痛点&#xff1a;…

作者头像 李华