React 引入了新的表单 Actions 和相关的钩子来增强原生表单并简化客户端-服务器通信。这些功能使开发人员能够更有效地处理表单提交,从而提高用户体验和代码可维护性。对于React form Actions的深入探索,你可以参考我关于React Form Actions的文章中的详细文章。
React 18 引入了服务器组件功能。服务器组件不是服务器端渲染(SSR),服务器组件在运行时和构建时都在服务器上专门执行。这些组件可以访问服务器端资源,例如数据库和文件系统,但它们无法执行事件侦听器或挂钩等客户端操作。
为了演示服务器组件和服务器操作的功能,我们将使用 Next.js 和 Prisma。
Next.js 是一个用于构建全栈 Web 应用程序的 React 框架。您可以使用 React Components 来构建用户界面,并使用 Next.js 来实现附加功能和优化。在底层,Next.js 还抽象并自动配置 React 所需的工具,例如捆绑、编译等。这使您可以专注于构建应用程序,而不是花时间进行配置。了解更多
Prisma 是一种简化数据库访问和操作的 ORM,让您无需编写 SQL 即可查询和操作数据。了解更多
初始设置
首先创建一个新的 Next.js 应用程序:
纱线创建下一个应用程序服务器示例
您的初始文件夹结构将如下所示:
升级到 Canary 版本以访问 React 19 功能,包括服务器操作:
yarn add next@rc react@rc react-dom@rc
安装 Prisma
yarn add prisma
Prisma 配置
在 src/lib/prisma/schema.prisma 创建 Prisma 模式文件:
generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = "file:./dev.db" } model User { id Int @id @default(autoincrement()) email String @unique name String? age Int }
出于演示目的,我们使用 SQLite。对于生产,您应该使用更强大的数据库。
接下来,在 src/lib/prisma/prisma.ts 添加 Prisma 客户端文件
// ts-ignore 7017 is used to ignore the error that the global object is not // defined in the global scope. This is because the global object is only // defined in the global scope in Node.js and not in the browser. import { PrismaClient } from '@prisma/client' // PrismaClient is attached to the `global` object in development to prevent // exhausting your database connection limit. // // Learn more: // https://pris.ly/d/help/next-js-best-practices const globalForPrisma = global as unknown as { prisma: PrismaClient } export const prisma = globalForPrisma.prisma || new PrismaClient() if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma export default prisma
在package.json中配置Prisma:
{ //other settings "prisma": { "schema": "src/lib/prisma/schema.prisma", "seed": "ts-node src/lib/prisma/seed.ts" } }
并更新 tsconfig.json 中的 TypeScript 设置:
{ //Other settings here... "ts-node": { // these options are overrides used only by ts-node // same as the --compilerOptions flag and the // TS_NODE_COMPILER_OPTIONS environment variable "compilerOptions": { "module": "commonjs" } } }
全局安装 ts-node:
yarn global add ts-node
播种初始数据
在 src/lib/prisma/seed.ts 添加种子文件以填充初始数据:
import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); async function main() { await prisma.user.create({ email: "anto@prisma.io", name: "Anto", age: 35, }); await prisma.user.create({ email: "vinish@prisma.io", name: "Vinish", age: 32, }); } main() .then(async () => { await prisma.$disconnect(); }) .catch(async (e) => { console.error(e); await prisma.$disconnect(); process.exit(1); });
安装 Prisma 客户端
yarn add @prisma/client
运行迁移命令:
yarn prisma migrate dev --name init
如果种子数据没有反映,请手动添加:
yarn prisma db seed
太棒了!由于安装已准备就绪,您可以创建一个执行数据库操作的操作文件。
创建服务器操作
服务器操作是一项强大的功能,可实现无缝的客户端-服务器相互通信。让我们在 src/actions/user.ts 创建一个用于数据库操作的文件:
"use server"; import prisma from '@/lib/prisma/prisma' import { revalidatePath } from "next/cache"; // export type for user export type User = { id: number; name: string | null; email: string; age: number; }; export async function createUser(user: any) { const resp = await prisma.user.create({ data: user }); console.log("server Response"); revalidatePath("/"); return resp; } export async function getUsers() { return await prisma.user.findMany(); } export async function deleteUser(id: number) { await prisma.user.delete({ where: { id: id, }, }); revalidatePath("/"); }
让我们创建一个 React 服务器组件来读取和渲染数据库中的数据。创建 src/app/serverexample/page.tsx:
import UserList from "./Users"; import "./App.css" export default async function ServerPage() { return ( <div classname="app"> <header classname="App-header"> <userlist></userlist> </header> </div> ); }
在 src/app/serverexample/App.css 添加一些样式
.App { text-align: center; } .App-logo { height: 40vmin; pointer-events: none; } .App-header { background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; } input { color: #000; } .App-link { color: #61dafb; }
创建组件来获取和渲染用户列表:
src/app/serverexample/UserList.tsx
import { getUsers } from "@/actions/user"; import { UserDetail } from "./UserDetail"; export default async function UserList() { //Api call to fetch User details const users = await getUsers(); return ( <div classname="grid grid-cols-3 gap-5"> {users.length ? ( users.map((user) => <userdetail user="{user}"></userdetail>) ) : ( <div classname="col-span3 opacity-60 text-sm text-center"> No User found </div> )} </div> ); }
src/app/serverexample/UserDetail.tsx
export function UserDetail({ user }) { return ( <div classname="flex items-center gap-4 border border-gray-600 py-1 px-4"> <img classname="w-10 h-10 rounded-full" src="https://api.dicebear.com/9.x/personas/svg?seed=Shadow" alt="React - 服务器操作"> <div classname="font-medium text-base dark:text-white"> <div>{user.name}</div> <div classname="text-sm text-gray-500 dark:text-gray-400"> {user.email} </div> </div> </div> ); }
运行开发服务器:
yarn dev
导航到 http://localhost:3000/serverexample 查看渲染的用户列表:
默认情况下,Next.js 中的组件是服务器组件,除非您指定“使用客户端”指令。注意两点:
服务器操作可实现无缝的客户端-服务器相互通信。让我们添加一个表单来创建新用户。
在 src/app/serverexample/AddUser.tsx 创建一个新文件:
"use client"; import "./app.css"; import { useActionState } from "react"; import { createUser } from "../../actions/user"; const initialState = { error: undefined, }; export default function AddUser() { const submitHandler = async (_previousState: object, formData: FormData) => { try { // This is the Server Action method that transfers the control // Back to the server to do DB operations and get back the result. const response = await createUser({ name: formData.get("name") as string, email: formData.get("email") as string, age: parseInt(formData.get("age") as string), }); return { response }; } catch (error) { return { error }; } }; const [state, submitAction, isPending] = useActionState( submitHandler, initialState ); return ( <div classname="mt-10"> <h4 classname="text-center">Add new User</h4>{" "} <form action="%7BsubmitAction%7D" classname="text-base"> <div classname="mt-6 text-right"> Name:{" "} <input classname="ml-2" required name="name" type="text" placeholder="Name"> </div> <div classname="mt-6 text-right"> Email:{" "} <input classname="ml-2" name="email" type="email" placeholder="Email"> </div> <div classname="mt-6 text-right"> Age:{" "} <input classname="ml-2" name="age" type="text" placeholder="Age"> </div> <div classname="mt-6 text-right"> <button disabled classname="bg-green-600 text-white px-5 py-1 text-base disabled:opacity-30"> {isPending ? "Adding" : "Add User"} </button> </div> {(state?.error as string) && <p>{state.error as string}</p>} </form> </div> ); }
更新 src/app/serverexample/page.tsx 以包含 AddUser 组件:
import UserList from "./UserList"; // Import new line import AddUser from "./AddUser"; import "./App.css" export default async function ServerPage() { return ( <div classname="app"> <header classname="App-header"> <userlist></userlist> {/* insert Add User here */} <adduser></adduser> </header> </div> ); }
运行应用程序现在将允许您通过表单添加新用户,并无缝处理服务器端处理。
The AddUser component is at the heart of this example, showcasing how React Server Actions can revolutionize the way we handle client-server interactions. This component renders a form for adding new users and leverages the useActionState hook to create a smooth and seamless bridge between the client-side interface and server-side operations.
How It Works
From the perspective of a developer working on the client side, it appears as if the form submission is handled locally. However, the heavy lifting such as database manipulation occurs on the server.
The useActionState hook encapsulates this process, managing the state transitions and handling errors, while maintaining an intuitive API for developers.
So that's with forms, now lets test an example without forms.
update src/app/serverexample/UserDetail.tsx
"use client"; import { deleteUser } from "@/actions/user"; import { useTransition } from "react"; export function UserDetail({ user }) { const [pending, startTransition] = useTransition(); const handleDelete = () => { startTransition(() => { deleteUser(user.id); }); }; return ( <div classname="flex items-center gap-4 border border-gray-600 py-1 px-4"> {pending ? ( <p>Deleting...</p> ) : ( <img classname="w-10 h-10 rounded-full" src="https://api.dicebear.com/9.x/personas/svg?seed=Shadow" alt="React - 服务器操作"> <div classname="font-medium text-base dark:text-white"> <div>{user.name}</div> <div classname="text-sm text-gray-500 dark:text-gray-400"> {user.email} </div> </div> <button classname="ml-auto" onclick="{handleDelete}"> <img classname="w-4 h-4" src="/delete.png" alt=""> </button> > )} </div> ); }
Key Points:
Now, you can seamlessly delete a user within the application:
This approach is transformative because it abstracts away the complexities of client-server communication. Traditionally, such interactions would require handling API endpoints, managing asynchronous requests, and carefully coordinating client-side state with server responses. With React Server Actions and the useActionState hook, this complexity is reduced, allowing developers to focus more on building features rather than worrying about the underlying infrastructure.
By using this pattern, you gain:
You can find the full code in the repository
以上是React - 服务器操作的详细内容。更多信息请关注PHP中文网其他相关文章!