如果在安装过程中遇到依赖项解析问题:
如果你没有使用 React Native,通过强制使用 --force
或 --legacy-peer-deps
来解决该问题。如果你在使用 React Native,则需要使用与 React Native 版本兼容的确切 React 版本。
本教程演示如何使用 Drizzle ORM 和 Neon 数据库 以及 Next.js 构建 Todo 应用
。
npx create-next-app@latest --typescript
npm i drizzle-orm
npm i -D drizzle-kit
npm i @neondatabase/serverless
dotenv
包用于管理环境变量。npm i dotenv
如果在安装过程中遇到依赖项解析问题:
如果你没有使用 React Native,通过强制使用 --force
或 --legacy-peer-deps
来解决该问题。如果你在使用 React Native,则需要使用与 React Native 版本兼容的确切 React 版本。
登录到 Neon 控制台 并导航到项目部分。选择一个项目或点击 新建项目
按钮以创建一个新项目。
你的 Neon 项目自带一个名为 neondb
的现成 Postgres 数据库。我们将在本教程中使用它。
导航到项目控制台中的 连接详情 部分以查找你的数据库连接字符串。它应该类似于下面这样:
postgres://username:password@ep-cool-darkness-123456.us-east-2.aws.neon.tech/neondb
将 DATABASE_URL
环境变量添加到你的 .env
或 .env.local
文件中,你将在此用来连接 Neon 数据库。
DATABASE_URL=NEON_DATABASE_CONNECTION_STRING
在 src/db
文件夹中创建 drizzle.ts
文件,并设置你的数据库配置:
import { config } from "dotenv";
import { drizzle } from 'drizzle-orm/neon-http';
config({ path: ".env" }); // 或 .env.local
export const db = drizzle(process.env.DATABASE_URL!);
import { integer, text, boolean, pgTable } from "drizzle-orm/pg-core";
export const todo = pgTable("todo", {
id: integer("id").primaryKey(),
text: text("text").notNull(),
done: boolean("done").default(false).notNull(),
});
在这里,我们使用来自 Drizzle ORM 的数据类型定义 todo
表,其中包含字段 id
、text
和 done
。
Drizzle 配置 - 这是一个用于 Drizzle Kit 的配置文件,包含有关你的数据库连接、迁移文件夹和模式文件的所有信息。
在项目根目录中创建一个 drizzle.config.ts
文件,并添加以下内容:
import { config } from 'dotenv';
import { defineConfig } from "drizzle-kit";
config({ path: '.env' });
export default defineConfig({
schema: "./src/db/schema.ts",
out: "./migrations",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
你可以使用 drizzle-kit generate
命令生成迁移,然后使用 drizzle-kit migrate
命令运行它们。
生成迁移:
npx drizzle-kit generate
这些迁移存储在 drizzle/migrations
目录中,如你的 drizzle.config.ts
所指定的。这一目录将包含更新数据库模式所需的 SQL 文件以及一个 meta
文件夹,用于存储在不同迁移阶段的模式快照。
生成迁移的示例:
CREATE TABLE IF NOT EXISTS "todo" (
"id" integer PRIMARY KEY NOT NULL,
"text" text NOT NULL,
"done" boolean DEFAULT false NOT NULL
);
运行迁移:
npx drizzle-kit migrate
另外,你可以使用 Drizzle kit push 命令 直接将更改推送到数据库:
npx drizzle-kit push
在本步骤中,我们在 src/actions/todoAction.ts 文件中建立服务器端函数,以处理 todo 项目的重要操作:
getData
:
addTodo
:
revalidatePath("/")
触发主页的重新验证。deleteTodo
:
toggleTodo
:
editTodo
:
"use server";
import { eq, not } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { db } from "@/db/drizzle";
import { todo } from "@/db/schema";
export const getData = async () => {
const data = await db.select().from(todo);
return data;
};
export const addTodo = async (id: number, text: string) => {
await db.insert(todo).values({
id: id,
text: text,
});
};
export const deleteTodo = async (id: number) => {
await db.delete(todo).where(eq(todo.id, id));
revalidatePath("/");
};
export const toggleTodo = async (id: number) => {
await db
.update(todo)
.set({
done: not(todo.done),
})
.where(eq(todo.id, id));
revalidatePath("/");
};
export const editTodo = async (id: number, text: string) => {
await db
.update(todo)
.set({
text: text,
})
.where(eq(todo.id, id));
revalidatePath("/");
};
在 src/types/todoType.ts
中定义一个表示 todo 项目的 TypeScript 类型,包含三个属性: id
类型为 number
、 text
类型为 string
和 done
类型为 boolean
。这个类型命名为 todoType
,表示你应用中一个典型 todo 项目的结构。
export type todoType = {
id: number;
text: string;
done: boolean;
};
src/components/todo.tsx
:
创建一个 Todo
组件,表示单个 todo 项目。它包括用于显示和编辑 todo 文本、用复选框标记为完成的功能,以及提供编辑、保存、取消和删除 todo 的操作。src/components/addTodo.tsx
:
AddTodo
组件提供了一个简单的表单,用于向 Todo 应用添加新的 todo 项目。它包括一个输入字段用于输入 todo 文本,以及一个按钮用于触发新 todo 的添加。src/components/todos.tsx
:
创建 Todos 组件,表示 Todo 应用的主要界面。它管理 todo 项目的状态,提供创建、编辑、切换和删除 todo 的功能,并使用 Todo
组件渲染各个 todo 项目。"use client";
import { ChangeEvent, FC, useState } from "react";
import { todoType } from "@/types/todoType";
interface Props {
todo: todoType;
changeTodoText: (id: number, text: string) => void;
toggleIsTodoDone: (id: number, done: boolean) => void;
deleteTodoItem: (id: number) => void;
}
const Todo: FC<Props> = ({
todo,
changeTodoText,
toggleIsTodoDone,
deleteTodoItem,
}) => {
// 处理编辑模式的状态
const [editing, setEditing] = useState(false);
// 处理文本输入的状态
const [text, setText] = useState(todo.text);
// 处理完成状态的状态
const [isDone, setIsDone] = useState(todo.done);
// 文本输入变化的事件处理
const handleTextChange = (e: ChangeEvent<HTMLInputElement>) => {
setText(e.target.value);
};
// 切换完成状态的事件处理
const handleIsDone = async () => {
toggleIsTodoDone(todo.id, !isDone);
setIsDone((prev) => !prev);
};
// 启动编辑模式的事件处理
const handleEdit = () => {
setEditing(true);
};
// 保存编辑文本的事件处理
const handleSave = async () => {
changeTodoText(todo.id, text);
setEditing(false);
};
// 取消编辑模式的事件处理
const handleCancel = () => {
setEditing(false);
setText(todo.text);
};
// 删除 todo 项目的事件处理
const handleDelete = () => {
if (confirm("你确定要删除这个 todo 吗?")) {
deleteTodoItem(todo.id);
}
};
// 渲染 Todo 组件
return (
<div className="flex items-center gap-2 p-4 border-gray-200 border-solid border rounded-lg">
{/* 用于标记 todo 为完成的复选框 */}
<input
type="checkbox"
className="text-blue-200 rounded-sm h-4 w-4"
checked={isDone}
onChange={handleIsDone}
/>
{/* todo 文本的输入字段 */}
<input
type="text"
value={text}
onChange={handleTextChange}
readOnly={!editing}
className={`${
todo.done ? "line-through" : ""
} outline-none read-only:border-transparent focus:border border-gray-200 rounded px-2 py-1 w-full`}
/>
{/* 编辑、保存、取消和删除的操作按钮 */}
<div className="flex gap-1 ml-auto">
{editing ? (
<button
onClick={handleSave}
className="bg-green-600 text-green-50 rounded px-2 w-14 py-1"
>
保存
</button>
) : (
<button
onClick={handleEdit}
className="bg-blue-400 text-blue-50 rounded w-14 px-2 py-1"
>
编辑
</button>
)}
{editing ? (
<button
onClick={handleCancel}
className="bg-red-400 w-16 text-red-50 rounded px-2 py-1"
>
关闭
</button>
) : (
<button
onClick={handleDelete}
className="bg-red-400 w-16 text-red-50 rounded px-2 py-1"
>
删除
</button>
)}
</div>
</div>
);
};
export default Todo;
更新 src/app/page.tsx
文件以从数据库获取 todo 项目并渲染 Todos
组件:
import { getData } from "@/actions/todoAction";
import Todos from "@/components/todos";
export default async function Home() {
const data = await getData();
return <Todos todos={data} />;
}
本指南使用以下文件结构:
📦 <项目根目录>
├ 📂 migrations
│ ├ 📂 meta
│ └ 📜 0000_heavy_doctor_doom.sql
├ 📂 public
├ 📂 src
│ ├ 📂 actions
│ │ └ 📜 todoActions.ts
│ ├ 📂 app
│ │ ├ 📜 favicon.ico
│ │ ├ 📜 globals.css
│ │ ├ 📜 layout.tsx
│ │ └ 📜 page.tsx
│ ├ 📂 components
│ │ ├ 📜 addTodo.tsx
│ │ ├ 📜 todo.tsx
│ │ └ 📜 todos.tsx
│ └ 📂 db
│ │ ├ 📜 drizzle.ts
│ │ └ 📜 schema.ts
│ └ 📂 types
│ └ 📜 todoType.ts
├ 📜 .env
├ 📜 .eslintrc.json
├ 📜 .gitignore
├ 📜 drizzle.config.ts
├ 📜 next-env.d.ts
├ 📜 next.config.mjs
├ 📜 package-lock.json
├ 📜 package.json
├ 📜 postcss.config.mjs
├ 📜 README.md
├ 📜 tailwind.config.ts
└ 📜 tsconfig.json