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中文網其他相關文章!