React Table,更准确地说,TanStack Table,是一个无头的 UI 库。如果你对这类产品不太熟悉,你可能会疑惑,这“无头 UI”到底是什么意思?毕竟,UI 不是应该和头部有关吗?当你开始用 React Table 实际操作时,这些概念就变得清晰明了了。
有以下两个原因,表格是构建网页UI时最难处理的问题之一。首先,尤其是考虑到它们可以容纳的数据量、允许的交互以及适应不同屏幕尺寸的需要,它们很难被很好地渲染。其次,它们的状态很复杂:排序、过滤、分页、分组等等。React Table 的理念是很好地解决第二个问题,并且将第一个问题完全留给您处理。它管理表格的状态和逻辑,但不涉及渲染。因此,它不涉及渲染部分。
创建UI是一个非常个性化且定制化的体验,哪怕这意味着选择一个设计系统或遵循设计规范。——来自tanstack.com
表格最常用于展示数据库查询结果——在现代,这通常是指 ORM 的输出结果。这篇文章将教你如何连接 Prisma — 最受欢迎的 TypeScript ORM,到 React Table,借助于 React Query 和 ZenStack。你会发现其实只需要少量代码就可以渲染一个完整的表格 UI。
一个全栈搭建我们需要一个全栈应用来查询数据库并渲染用户界面。在这个例子中,我将使用Next.js作为框架,虽然这种方法也可以应用于其他类似的框架,例如Nuxt或SvelteKit,或者应用于前后端分离的应用程序中,比如将前端和后端分开的应用程序。
我们可以很容易地使用 npx create-next-app
来创建一个新的项目。之后,我们需要安装几个必要的依赖项。
- Prisma — 一个 ORM
- ZenStack — 构建在 Prisma 之上的全栈工具
- React Query — 数据获取库
- React Table — 无头表格组件库
我们将也使用微软多年前创建的著名“北风”贸易数据集来提供数据给我们的数据库。这里展示了其ERD(实体关系图):
[ERD 图表描述应保持不变或根据实际情况以图表形式展示]
一个 [Prisma 架构文件(schema.prisma)] 编写用于反映这个数据库结构的。
免费午餐 APISQL 数据库并不是为前端直接使用而设计的。你需要一个API来作为中介。你可以用多种方法来构建这样的API,但在这里我们将使用 ZenStack 来“逆向构建”它。ZenStack 是一个建立在 Prisma 之上的全栈工具包,它的一个很酷的功能是能自动从模式中推导出后端API。
设置 ZenStack 的很简单:
- 运行
npx zenstack init
来初始化项目。它会将schema.prisma
文件复制为schema.zmodel
,后者是 ZenStack 使用的模式文件。ZModel 是 Prisma 模式的一个扩展。 - 每次你修改
schema.zmodel
文件时,运行npx zenstack generate
命令以重新生成 Prisma 模式以及PrismaClient
。
ZenStack 可用几行代码为 Next.js 实现一整套 CRUD API 功能:
// src/app/api/model/[…path]/route.ts
// 导入 prisma 模型
import { prisma } from '@/server/db';
// 导入 NextRequestHandler 函数
import { NextRequestHandler } from '@zenstackhq/server/next';
// 定义一个 NextRequestHandler 实例
const handler = NextRequestHandler({
// 获取 prisma 模型实例
getPrisma: () => prisma,
// 启用应用目录功能
useAppDir: true,
});
// 导出 DELETE, GET, PATCH, POST, PUT 方法
export {
handler as DELETE,
handler as GET,
handler as PATCH,
handler as POST,
handler as PUT,
};
现在你有一组置于 /api/model
上的 API(API接口),功能类似 PrismaClient(Prisma客户端)。
- GET
/api/model/order/findMany?q=...
(获取多个订单) - GET
/api/model/order/count?q=...
(获取订单总数) - PUT
/api/model/order/update
(更新订单) - …
查询参数和请求体也符合相应的 PrismaClient
方法参数要求。
我知道你心里有一个大大的 🚨 NO THIS IS NOT SECURE 🚨 在闪现。等等,我们稍后再说这个。
免费午餐钩子[] https://zenstack.dev/blog/react-table#the-free-lunch-hooks有个免费的 API 很酷,但用 fetch
调用它却挺麻烦的。那有没有一些免费的查询钩子就好了呢?是的,在 ZModel 中加入 @zenstackhq/tanstack-query
插件,你就可以为每个模型生成一组完全类型的 React Query 钩子了。
// schema.zmodel
插件 钩子 {
提供插件 = '@zenstackhq/tanstack-query'
目标框架 = 'react'
输出目录 = 'src/hooks'
}
这些钩子调用了我们在上一节中引入的 API,这些API也与 PrismaClient
的接口定义一致。
import { useFindManyOrder } from '@/hooks/order';
// data 的类型是 `(Order & { details: OrderDetail[]; customer: Customer })[]`
const { data, isLoading, error } = useFindManyOrder({
where: { ... },
orderBy: { ... },
include: { details: true, customer: true }
});
需要注意的是,虽然 React Query 和 React Table 都来自 TanStack,不过你并不一定要一起使用它们。React Table 并不依赖于特定的数据获取方式。它们配合起来非常默契。
最后,让我们来建个表格。
创建一个基本的表格其实很容易。首先,定义列,接着用数据初始化一个表格实例。我们通过构建一个展示订单详情的表格来演示具体操作。
// 查询 `OrderDetail` 时包含的关系字段
const queryInclude = {
include: {
order: { include: { employee: true } },
product: { include: { category: true } },
},
} 满足 Prisma.OrderDetailFindManyArgs 接口要求;
// 创建一个列辅助器来简化列定义
// `Prisma.OrderDetailGetPayload<typeof queryInclude>` 类型给我们
// 提供了查询结果的形状
const columnHelper =
createColumnHelper<Prisma.OrderDetailGetPayload<typeof queryInclude>>();
const columns = [
columnHelper.accessor('order.id', { header: () => <span>订单ID</span> }),
columnHelper.accessor('order.orderDate', {
cell: (info) => info.getValue()?.toLocaleDateString(),
header: () => <span>日期</span>,
}),
// 其他列...
columnHelper.accessor('order.employee.firstName', {
header: () => <span>员工</span>,
}),
];
export const OrderDetails = () => {
// 使用查询钩子来获取数据
const { data } = useFindManyOrderDetail({
...queryInclude,
});
// 创建一个表格实例对象
const table = useReactTable({
data: orders ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
});
}
我们可以使用一些基本的tsx代码来渲染表格:
export const 订单详情组件 = () => {
// React组件,用于展示订单详情
...
return (
<table>
<thead>
// 表格的表头部分
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id}>
{动态渲染(
header.column.columnDef.header,
header.getContext()
)}
</th>
))}
</tr>
))}
</thead>
<tbody>
// 获取行模型
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
// 获取可见单元格
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{动态渲染(
cell.column.columnDef.cell,
cell.getContext()
)}
</td>
))}
</tr>
))}
</tbody>
);
}
借助列定义的指引,React Table 知道如何为一个单元格获取数据并根据需要转换数据。你只需要关注如何正确布局表格。
React Table 很酷的一点是,你不需要将嵌套的查询结果展平为表格形式,列可以被定义为深入访问嵌套的对象。
让它更高级表格不仅可以用来查看数据,还能做许多其他的事情。我们以分页为例,演示如何在我们的系统中启用这种交互。
React Query 内置了前端分页功能。然而,由于我们要渲染数据库中的表格,我们希望分页操作在后端进行。首先,我们定义分页状态,并将表格设置为手动分页模式(即我们自己负责分页操作)。
// 分页状态:
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: PAGE_SIZE,
});
// 查询总行数
const { data: count } = useCountOrderDetail();
const table = useReactTable({
...
// 分页
manualPagination: true,
onPaginationChange: setPagination,
pageCount: Math.ceil((count ?? 0) / PAGE_SIZE),
// 状态:pagination
state: { pagination },
});
同时更新钩子调用以考虑分页状态:
const { data } = useFindManyOrderDetail({
...查询条件,
skip: 分页参数.pageIndex * 分页参数.pageSize,
take: 分页参数.pageSize,
});
最后,添加导航按钮吧:
<div>
<button onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}>
上一页
</button>
<button onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}>
下一页
</button>
<span className="ml-2">
当前是第 {table.getState().pagination.pageIndex + 1} 页,共 {table.getPageCount().toLocaleString()} 页
</span>
</div>
这一部分很好地说明了“无头”UI的价值。你不再需要详细管理分页。相反,只需提供最基本的功能逻辑,剩下的由React Table来处理。排序也可以用类似的方法实现。你可以通过文章末尾的链接查看完整代码。
我们现在有一个非常酷的从头到尾运作的表格,大约有200行的代码。简洁的代码只是这种组合的好处之一。它还为每层提供了出色的灵活性。
- Prisma的查询
Prisma以其简洁而强大的查询API而闻名。它允许你执行复杂的连接和聚合操作而不必编写SQL。在我们的示例中,我们的表格从五个表中显示数据,而我们几乎感觉不到其复杂性。 - ZenStack的访问控制
还记得我说过我们会回到安全问题上吗?一个实际的API必须具备授权机制。ZenStack的强大之处在于其定义访问控制规则的能力,这些规则可以直接在数据模式中定义。你可以定义诸如拒绝匿名用户或仅显示当前登录员工的订单之类的规则等。了解更多详情 这里。 - React Query的数据获取
React Query在数据如何获取、缓存和失效方面提供了极大的灵活性。利用其功能来构建一个高度响应的界面,并同时减少对数据库的负载。 - React Table的状态管理
React Table将表格状态的每个方面都组织得井井有条。它提供了一个坚实的设计模式让你遵循,而不限制你如何渲染表格UI。
开发工具的演变就像是一个钟摆,在简单性和灵活性之间来回摆动。多年来的智慧结晶,造就了许多出色的工具,例如 React Table 和 React Query。虽然它们不是最容易上手的,但足够简单且非常灵活。它们虽然不是最简单的,但非常灵活且易于使用。
我们在构建 ZenStack,一个通过添加强大的访问控制层来增强 Prisma ORM 的工具包,并释放其在全栈开发中的全部潜力。如果你觉得这个项目有趣,请帮忙给它点星,让更多的人发现它!
共同學(xué)習(xí),寫下你的評(píng)論
評(píng)論加載中...
作者其他優(yōu)質(zhì)文章