将 AG Grid React UI 与 Remix.run 一起使用
[
](https://res.cloudinary.com/practicaldev/image/fetch/s--Zna-2_0K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880 /https://blog.ag-grid.com/content/images/2022/05/remix-run-blog-ag-grid.png)
这篇文章由Arek Nawo贡献给 AG Grid 博客。
Remix是一个基于React Router的全新全栈 JavaScript 框架,旨在通过渐进增强和 Web 基础将快速且有弹性的用户体验 (UX) 与高质量的开发体验 (DX) 相结合。
在本教程中,您将学习如何将 Remix 与AG Grid(一个高级、高性能的 JavaScript 网格库)一起使用,以创建涉及大型数据表和实时数据流的各种应用程序。感谢 Remix 和 AG Grid,您将立即准备好前端和后端!
简而言之,这是该应用程序的工作方式:
[
](https://res.cloudinary.com/practicaldev/image/fetch/s---WAYtlaD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.ag -grid.com/content/images/2022/05/remix_run_app.png)
您可以使用这个 GitHub 存储库跟随。
混音功能
在进入代码之前,您应该对 Remix 的架构和优势有一个很好的了解。让我们仔细看看。
混音架构
Remix 是建立在Web Fetch API之上的全栈框架,专注于服务器端渲染(SSR)。它将您的前端和后端紧密结合在一起,每条路由都可以同时成为 UI 和 API。最重要的是,由于对渐进增强的一流支持,Remix 可以服务于任何 Web 环境,无论是否使用 JavaScript,逐步应用现代功能和优化,同时保持核心简单并符合 Web 基础。
嵌套路由
Remix 的突出特点之一是嵌套路由。由于底层的React Router,Remix 可以为整个页面或仅更改部分预取数据和资源。这会带来更快的加载时间和更好的用户体验,在与 Web 应用程序交互时几乎无需等待。此外,每个嵌套路由都有专用的错误边界,您可以确保您的错误仅影响应用程序的给定部分而不是整个应用程序。
其他特性
除了嵌套路由之外,Remix 还包含其他强大的功能,例如处理表单、提高可访问性和优化 UI。所有这些都基于 Web 标准,并且随着渐进增强,您可以为用户提供最佳体验。
将 Remix 与 AG Grid 集成
要开始使用 Remix,请确保您已安装Node.jsv14 和npmv7 或更新版本。要快速设置新项目,请使用Remix CLI。
使用以下命令启动您的项目:
npx create-remix
进入全屏模式 退出全屏模式
运行此命令将提示您输入要使用的目录和模板。对于本教程,选择 Just the basics 和 Remix App Server。
设置项目后,转到其目录并安装其他依赖项:
npm install ag-grid-react ag-grid-community @prisma/client
npm install -D prisma
进入全屏模式 退出全屏模式
依赖项包括设置 AG Grid 和Prisma所需的一切,这是一个现代 Node.js ORM(对象关系映射工具)。
设置 Prisma
在进入 Remix 之前,您首先必须初始化 Prisma 并将其连接到您的数据库。本教程将使用SQLite,但 Prisma 适用于许多关系数据库,甚至MongoDB。
首先启动 Prisma:
npx prisma init
进入全屏模式 退出全屏模式
上面的命令将创建一个新的prisma目录,其中包含一个schema.prisma文件,以及一个包含数据库连接字符串的项目文件的根目录中的.env。
对于 SQLite,在.env文件中提供数据库所在位置的路径:
DATABASE_URL="file:./dev.db"
进入全屏模式 退出全屏模式
定义架构
在prisma/schema.prisma中,指定您的数据源,以及所有必要的数据模型:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
title String
content String?
author Author @relation(fields: [authorId], references: [id])
authorId Int
}
model Author {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
进入全屏模式 退出全屏模式
Prisma 架构文件有自己的语法和规则,但即使您对 Prisma 没有太多经验,它们也很容易阅读。上述架构指定以下内容:
-
Generator 用于数据库客户端,它将专门为您的数据模型输出定制的客户端库。
-
数据源 提供有关应使用的数据库的详细信息以及任何所需的凭据。
-
Post和Author模型具有不同的属性——如String类型的title。所有类型都将映射到指定数据库使用的类型。最重要的是,通过利用@relation属性建立了两个模型之间的简单关系。
应用架构
准备好模式后,您现在可以使用 Prisma CLI 生成迁移并使用prisma migrate命令将它们应用到您的数据库:
npx prisma migrate dev --name init
进入全屏模式 退出全屏模式
最后,运行prisma generate以生成与您的架构匹配的专用客户端库:
npx prisma generate
进入全屏模式 退出全屏模式
创建资源路由
要从您的 Remix 应用程序连接 Prisma,您必须使用先前生成的 Prisma 客户端。首先在app文件夹中创建一个新的db.server.js文件:
// app/db.server.ts
import { PrismaClient } from "@prisma/client";
let prisma;
if (process.env.NODE_ENV === "production") {
prisma = new PrismaClient();
} else {
// Reuse the client when development server restarts
if (!global.dbClient) {
global.dbClient = new PrismaClient();
}
prisma = global.dbClient;
prisma.$connect();
}
export { prisma };
进入全屏模式 退出全屏模式
上面的模块导出了一个PrismaClient的实例。通过一些额外的处理,该实例被缓存并在 Remix 服务器重新启动时重用,以优化开发过程。
添加帖子资源路由
要使用导出的 Prisma 客户端实例,在app/routes/posts.js中新建资源路由:
// app/routes/posts.js
import { prisma } from "../db.server";
export async function loader({ request }) {
const from = Number(new URL(request.url).searchParams.get("from"));
const to = Number(new URL(request.url).searchParams.get("to"));
if (from >= 0 && to > 0) {
const posts = await prisma.post.findMany({
skip: from,
take: to - from,
select: {
id: true,
title: true,
updatedAt: true,
author: {
select: {
email: true,
name: true,
},
},
},
});
return posts;
}
return [];
}
进入全屏模式 退出全屏模式
在 Remix 中,资源路由是不属于你的 UI 的路由——也就是说,它不渲染任何组件。相反,它仅用于您网站其他部分使用的数据处理、服务和资源。
在上面的代码中,资源加载器与 Prisma 客户端一起使用。它返回从数据库中查询帖子列表得到的 JSON 数据。
分页被实现为路由将被 AG Grid 使用到延迟加载数据。使用from和toURL 参数以及Prisma 的偏移分页(skip和take属性)允许数据以网格需要的块加载。
使用select字段,您可以选择要包含在查询结果中的确切字段,包括来自关系的字段,如author属性所示。
添加测试数据
在生产应用程序中,用户将通过提供的前端填充您的数据库。但是,在测试连接和数据获取机制时,值得拥有一些可以使用的示例数据。
对于本教程,您可以使用来自GitHub 存储库的预填充 SQLite 数据库文件。或者,您可以创建一个专用的 Node.js 脚本并使用它来填充数据库,如下所示:
// test-data.js
import { prisma } from "./app/db.server.js";
import { faker } from "@faker-js/faker";
const generateTestData = async (numberOfPosts) => {
const author = await prisma.author.create({
data: {
email: faker.internet.exampleEmail(),
name: faker.name.findName(),
},
});
for (let i; i < numberOfPosts; i++) {
await prisma.post.create({
data: {
title: faker.lorem.sentence(getRandomInt(5, 8)),
content: faker.lorem.paragraph(),
author: { connect: { id: author.id } },
},
});
}
};
generateTestData(1000);
进入全屏模式 退出全屏模式
该脚本使用Faker.js 库生成并用假数据填充数据库。此外,值得注意的是 Prisma 的createMany方法是不受 SQLite支持,因此必须在循环中单独创建帖子。
您可以使用以下命令运行脚本,从而填充数据库文件:
node test-data.js
进入全屏模式 退出全屏模式
连接AG Grid
准备好数据源后,就可以将其连接到前端 AG Grid。首先创建一个 CSS 文件以确保正确显示网格:
/* app/styles.css */
html {
height: 100%;
width: 100%;
}
body {
margin: 0;
padding: 1rem;
height: calc(100% - 2rem);
width: calc(100% - 2rem);
}
进入全屏模式 退出全屏模式
在 Remix 中,CSS 文件是通过从特定路径导出专用的links函数来加载的。由于此 CSS 应应用于整个网站,因此您应将该函数放在app/root.jsx文件中:
// app/root.jsx
// ...
import styles from "./styles.css";
// ...
export function links() {
return [{ rel: "stylesheet", href: styles }];
}
进入全屏模式 退出全屏模式
定义路由结构
整个网格和数据获取逻辑将适合默认的app/routes/index.jsx文件。
首先定义路由的结构:
// app/routes/index.js
import { useCallback, useEffect, useState } from "react";
import { AgGridReact } from "ag-grid-react";
import AgGridStyles from "ag-grid-community/dist/styles/ag-grid.css";
import AgThemeAlpineStyles from "ag-grid-community/dist/styles/ag-theme-alpine.css";
import { useFetcher } from "remix";
// ...
const columnDefs = [
/* ... */
];
export default function Index() {
const onGridReady = useCallback((params) => {
// ...
}, []);
return (
<div className="ag-theme-alpine" style={{ width: "100%", height: "100%" }}>
<AgGridReact
columnDefs={columnDefs}
rowModelType="infinite"
onGridReady={onGridReady}
></AgGridReact>
</div>
);
}
export function links() {
return [
{ rel: "stylesheet", href: AgGridStyles },
{ rel: "stylesheet", href: AgThemeAlpineStyles },
];
}
进入全屏模式 退出全屏模式
可以看到AG Grid的默认样式和Alpine主题已经通过links函数加载完毕。
网格本身使用无限行模型,它实现了无限滚动机制,以便在用户滚动时延迟加载新行。这就是之前创建的资源路由的用武之地。
columnDefs定义了网格列的外观以及应该如何构建它们。
最后,onGridReady是一个回调,您可以在其中启动数据源连接。
连接数据源
在 Remix 中,初始加载后获取的与导航过程无关的数据应使用useFetcher挂钩处理。与useState一起使用来构造组件的状态:
const [isFetching, setIsFetching] = useState(false);
const [getRowParams, setGetRowParams] = useState(null);
const posts = useFetcher();
// ...
进入全屏模式 退出全屏模式
然后,在onGridReady回调中,创建并设置datasource:
// ...
const onGridReady = useCallback((params) => {
const datasource = {
getRows(params) {
if (!isFetching) {
posts.load(`/posts?from=${params.startRow}&to=${params.endRow}`);
setGetRowParams(params);
setIsFetching(true);
}
},
};
params.api.setDatasource(datasource);
}, []);
// ...
进入全屏模式 退出全屏模式
datasource是一个配置对象可以定义多个属性,其中最重要的是getRows,因为它负责实际获取数据。
在上面的代码中,只有在没有其他数据提取正在进行时,才会从/posts资源路由中提取数据。 fetcher 的load方法将首先获取数据,然后将其保存在其data属性中。因此,作为getRows方法的params一部分的successCallback将保存在getRowParams中以供以后使用。
要在网格上设置datasource,请使用提供给回调的对象的api.setDatasource方法:
useEffect(() => {
if (getRowParams) {
const data = posts.data || [];
getRowParams.successCallback(
data,
data.length < getRowParams.endRow - getRowParams.startRow
? getRowParams.startRow
: -1
);
}
setIsFetching(false);
setGetRowParams(null);
}, [posts.data]);
进入全屏模式 退出全屏模式
上面代码中的useEffect挂钩将在 fetcher 加载新数据时触发。如果successCallback可用,它将调用它,传递加载的数据和要加载的最后一行(如果知道)。该过程完成后,状态属性被重置以准备好进行另一次数据提取。
显示列
数据已经加载后,您只需告诉 AG Grid 它应该如何显示数据。你可以通过AgGridReact组件的columnDefs属性来做到这一点:
// ...
const dateFormatter = (params) => {
if (params.value) {
return new Date(params.value).toLocaleString();
}
return " ";
};
const columnDefs = [
{ field: "id" },
{ field: "title", flex: 1, minWidth: 400 },
{ field: "author.email", minWidth: 250 },
{ field: "author.name" },
{
field: "updatedAt",
valueFormatter: dateFormatter,
},
];
// ...
进入全屏模式 退出全屏模式
AG Grid Column Definitions (columnDefs) 是一组具有不同属性的配置对象,其中最重要的是field,因为它将数据属性与实际列“关联”。您可以使用点语法 (.value) 连接嵌套属性。
minWidth和flex属性定义了列的宽度应该如何缩放。minWidth指定列在px中的最小宽度,而flex使列填满所有可用空间。
对于包含需要进一步处理或格式化的数据的列,您可以提供valueFormatter。在上面的示例中,它用于将 ISO 数据转换为对用户更友好的语言环境字符串。
最终结果应如下所示:
[
集成](https://res.cloudinary.com/practicaldev/image/fetch/s--5GJ0LX6---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https:// blog.ag-grid.com/content/images/2022/05/anim-gridO27WOD6.gif)
您可以在CodeSandbox上查看现场演示。
使用 Cypress 进行端到端测试
虽然应用程序现已准备就绪,但您仍应对其进行测试,以确保最终用户获得无错误的体验。为此,您可以使用现代端到端 (E2E) 测试框架Cypress。
要开始使用 Cypress,首先安装必要的开发依赖项:
npm install -D cypress start-server-and-test
进入全屏模式 退出全屏模式
除了 Cypress 本身,start-server-and-test是一个简单的实用程序命令,可以通过单个命令轻松启动开发服务器和 E2E 测试套件。
安装依赖项后,在项目的根目录下创建一个cypress.json配置文件:
{
"baseUrl": "http://localhost:3000",
"integrationFolder": "cypress/e2e"
}
进入全屏模式 退出全屏模式
该配置为您的测试套件设置基本 URL 以及集成测试的位置。
在cypress/e2e文件夹中,您可以放置所有 E2E 测试套件。以下面的grid.test.js测试为例:
// cypress/e2e/grid.test.js
describe("Grid test", () => {
it("Should contain rows", () => {
cy.visit("/");
const element = cy.get("div.ag-center-cols-container");
element.children().should("have.length.above", 0);
});
});
进入全屏模式 退出全屏模式
该测试使用 Cypress API 首先进入开发服务器主机,然后检查表是否包含任何列。
要运行测试,请使用start-server-and-test命令:
npx start-server-and-test dev http://localhost:3000 "cypress open"
进入全屏模式 退出全屏模式
此命令将通过dev命令启动您的开发服务器,然后打开 Cypress。
生产部署
得益于 Remix 灵活的架构,它可以部署到多种环境——包括无服务器平台、容器和 Node.js 服务器。话虽如此,Remix 并没有在底层平台上构建抽象;它允许您访问平台的所有功能,但在部署到不同目标时也需要进行一些更改。
本教程中的应用程序是为 Remix App Server 配置的,它构建在Express之上。
在这种情况下,对于生产就绪设置,您需要做的就是创建一个生产构建并启动 Node.js 服务器:
npm run build
npm run start
进入全屏模式 退出全屏模式
在服务器运行的情况下,设置一个反向代理,如Nginx以允许外部访问您的服务器,您就可以开始了!
结论
在本教程中,您学习了如何结合 Remix 和 AG Grid 来创建快速且以用户为中心的数据处理应用程序。您已经使用了 Remix 的一些独特功能,并了解了它如何与其他工具集成,例如 Prisma ORM 或 Cypress E2E 测试框架。
本教程的完整源代码可以在这里找到。
AG Grid是一个易于设置的高性能 JavaScript 表格库。它具有强大的开箱即用功能,例如大数据处理、实时数据流和图表。
更多推荐

所有评论(0)