使用 Next.js + Remult 创建一个待办事项(二)
Yakim Zhang

上篇搭建来基础配置,本篇来定义实体类。

在 Task.ts 中定义实体类,包含 id,标题,是否完成。

Task 将使用实体类:

  • 作为客户端代码的模型类
  • 作为服务器端代码的模型类
  • 通过 remult 生成 API 端点、API 查询和数据库命令

我们正在创建的 Task 实体类将有一个自动生成的 UUIDid 字段,一个 title 字段和一个 completed 字段。实体的 API 路由(“任务”)将包括所有 CRUD 操作的端点。

定义模型

  1. shared 在文件夹下创建一个文件夹 src。此文件夹将包含前端和后端之间共享的代码。

  2. Task.ts 在文件夹中创建一个文件 src/shared/,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// src/shared/Task.ts

import { Entity, Fields } from "remult";

@Entity("tasks", {
allowApiCrud: true
})
export class Task {
@Fields.uuid()
id!: string;

@Fields.string()
title = '';

@Fields.boolean()
completed = false;
}
  1. 在服务器的 api 模块中,通过将实体添加到您传递给方法的对象来向 TaskRemult 注册实体:entities: [Task]optionscreateRemultServer()
1
2
3
4
5
6
7
8
//src/server/api.ts

import { createRemultServer } from "remult/server";
import { Task } from "../shared/Task";

export const api = createRemultServer({
entities: [Task]
})

@Entity 装饰器告诉 Remult 这个类是一个实体类。装饰器接受一个 key 参数(用于命名 API 路由并作为默认数据库集合/表名称),以及一个 options 用于定义与实体相关的属性和操作的参数,将在本教程的下一节中讨论。

为了最初允许任务的所有 CRUD 操作,我们将选项 allowApiCrud 设置为 true。

装饰@Fields.uuid 器告诉 Remult 使用自动生成一个 id uuid。我们将此属性标记为可选,这样我们就可以创建新对象而无需首先 Task 分配一个对象,并让 Remult 在对象的数据存储到后端数据库之前生成一个对象。id

@Fields.string 装饰器告诉 Remult 该 title 属性是 type 的实体数据字段 String。此装饰器还用于定义与字段相关的属性和操作,将在本教程的下一节中讨论,属性@Fields.boolean 也是如此 completed。

建立一些测试数据

现在 Task 定义了实体,我们可以用它来为数据库准备一些测试数据。

将突出显示的代码行添加到 src/server/api.ts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// src/server/api.ts

import { createRemultServer } from "remult/server";
import { Task } from "../shared/Task";

export const api = createRemultServer({
entities: [Task],
initApi: async remult => {
const taskRepo = remult.repo(Task);
if (await taskRepo.count() === 0) {
await taskRepo.insert([
{ title: "Task a" },
{ title: "Task b", completed: true },
{ title: "Task c" },
{ title: "Task d" },
{ title: "Task e", completed: true }
]);
}
}
})

在 initApi 建立数据库连接并且服务器准备好执行初始化操作之后,回调只会被调用一次。

taskRepo 是用于获取和创建实体对象的 Remult Repository 对象。Task

initApi 如果当前 count 为零,中的代码只是向数据库添加五个新任务。

保存更改将导致服务器重新启动并使用测试数据为数据库播种。tasks 在 http://localhost:3000/api/tasks 导航到 API 路由 (打开新窗口)查看数据。

我们首先将实体数据存储在后端 JSON 文件中。请注意,db 已在根文件夹下创建了一个 tasks.json 文件夹,其中包含一个包含创建的任务的文件。

显示任务列表

让我们通过在 React 组件中显示现有任务列表来开始开发 Web 应用程序。

将 的内容替换为 pages/index.tsx 以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// pages/index.tsx

import type { NextPage } from 'next'
import { useEffect, useState } from 'react';
import { remult } from 'remult';
import { Task } from '../src/shared/Task';

async function fetchTasks() {
return remult.repo(Task).find();
}

const Home: NextPage = () => {
const [tasks, setTasks] = useState<Task[]>([]);

useEffect(() => {
fetchTasks().then(setTasks);
}, []);

return (
<div>
<main>
{tasks.map(task => (
<div key={task.id}>
<input type="checkbox" checked={task.completed} />
{task.title}
</div>
))}
</main>
</div>
)
}

export default Home

以下是代码片段不同部分的快速概览:

该 fetchTasks 函数使用 Remult 存储库的 find 方法从服务器获取任务。
tasks 是一个任务数组 React 状态,用于保存任务列表。
React 的 useEffect hook 用于 fetchTasks 在 React 组件加载时调用一次。
浏览器刷新后,任务列表出现。

#添加样式
styles/globals.css 或者,通过使用此 CSS 文件替换 的内容,使应用看起来更好一些 (打开新窗口).

静态生成和服务器端渲染

Next.js 允许使用服务器端渲染 (SSR)或静态站点生成 (SSG)预渲染页面内容。

要从 getServerSideProp 或 getStaticProps 访问 remult 实例,您需要编写以下代码:

1
2
3
4
5
6
import { api } from '../src/server/api';

export const getServerSideProps: GetServerSideProps = async (context) => {
const remult = await api.getRemult(context);
return { props: {} };
};

然后您可以使用上面的代码片段获取数据 fetchTasks。

您的 pages/index.tsx 内容现在应该如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import type { NextPage } from 'next'
import { useEffect, useState } from 'react';
import { Task } from '../src/shared/Task';
import { api } from '../src/server/api';

type HomeProps = {
tasks: Task[];
};

const Home: NextPage = ({ tasks }: HomeProps) => {
return (
<div>
<main>
{tasks.map(task => (
<div key={task.id}>
<input type="checkbox" checked={task.completed} />
{task.title}
</div>
))}
</main>
</div>
)
}

export const getServerSideProps: GetServerSideProps = async (context) => {
// Get remult instance
const remult = await api.getRemult(context);

// Find all tasks
const tasks = await remult.repo(Task).find();

// Serialize tasks into plain object
const tasksJson = JSON.parse(JSON.stringify(tasks));

return { props: { tasks: tasksJson } };
};

export default Home

注意 const tasksJson = JSON.parse(JSON.stringify(tasks));

在 Next.js 中,您只能使用以下方式将可序列化对象传递给 props:

JSON.parse(JSON.stringify(data))
entityRef 的 toApiJson()方法,或
使用库配置 nextjs next-superjson

实体建立完成,下篇讲下如何利用 Remult 实现分页、排序和过滤。