更新資料
Next.js 鼓勵你搭配 Server Actions (即 React Server Functions)進行資料更新。你可以在 server component 中定義這些函式,然後從 client component 呼叫它們,以進行資料的新增、修改或刪除。
建立 Server Function
- 方法一: 另外開一個 action.js 檔案來管理 Server Actions。建立方法使用 Async Function 並在內部加上 ‘use server’;
1 2 3 4 5 6 7 8
| export async function createPost(formData) { 'use server' const title = formData.get('title') const content = formData.get('content') }
|
- 方法二: 直接在 Server Component 內加入 Server Function
1 2 3 4 5 6 7
| export default function Page() { async function createPost(formData: FormData) { 'use server' } return <></> }
|
在 Client Component 內呼叫 Server Function
在 Client Component 中 通過 <form action={myAction}>
或 <button onClick={...}>
呼叫這個 server action。
更新資料後,由於 server component 會被重新執行,所以畫面會重新渲染最新資料。
1 2 3 4 5 6
| 'use client' import { createPost } from '@/app/actions' export function Button() { return <button formAction={createPost}>Create</button> }
|
也可將 Server Function 透過 props 傳入 Client Component
1
| <ClientComponent updateItemAction={updateItem} />
|
1 2 3 4
| 'use client' export default function ClientComponent({ updateItemAction }) { return <form action={updateItemAction}>{/* ... */}</form> }
|
呼叫 Server Function
有兩種方式可以呼叫 Server Function。
- Form: 在 Server 或 Client Components 使用 Form
1 2 3 4 5 6 7 8 9 10 11
| import { createPost } from '@/app/actions' export function Form() { return ( <form action={createPost}> <input type="text" name="title" /> <input type="text" name="content" /> <button type="submit">Create</button> </form> ) }
|
- Event Handlers: 在 Client Components 內透過事件觸發
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 'use client' import { incrementLike } from './actions' import { useState } from 'react' export default function LikeButton({ initialLikes }) { const [likes, setLikes] = useState(initialLikes) return ( <> <p>Total Likes: {likes}</p> <button onClick={async () => { const updatedLikes = await incrementLike() setLikes(updatedLikes) }} > Like </button> </> ) }
|
Pending State
可以透過 useActionState 取得 Pending 狀態
1 2 3 4 5 6 7 8 9 10 11 12 13
| 'use client' import { useActionState, startTransition } from 'react' import { createPost } from '@/app/actions' import { LoadingSpinner } from '@/app/ui/loading-spinner' export function Button() { const [state, action, pending] = useActionState(createPost, false) return ( <button onClick={() => startTransition(action)}> {pending ? <LoadingSpinner /> : 'Create Post'} </button> ) }
|
錯誤捕捉
可以在 server function 裡回傳錯誤狀態
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| export async function createProduct(preState, formData) { const title = formData.get('title'); const price = formData.get('price'); const description = formData.get('description'); const errors = {}; if (!title) { errors.title = 'title is required' } if (!price) { errors.price = 'price is required' } if (!description) { errors.description = 'description is required' } if (Object.keys(errors).length > 0) { return { errors } }
await addProduct(title, parseInt(price), description); redirect('/products-db') }
|
前端 useActionState 的 state 中取得 error
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const [state, formAction, isPending] = useActionState(createProduct, {}); return ( <form action={formAction} className="p-4 space-y-4 max-w-96"> <div> <label> Title <input type="text" className="block w-full p-2 text-black border rounded" name="title" /> </label> { state.errors?.title && <p className="text-red-500">{state.errors?.title}</p>} </div> <button type="submit" className="block w-full p-2 text-white bg-blue-500 rounded disabled:bg-gray-500" disabled={isPending} > Add Product </button> </form> );
|
畫面刷新
畫面何時會自動更新、何時不會
情境 |
資料會自動更新嗎? |
說明 |
✅ Server Action + 使用 Server Component 顯示資料 |
✅ 會自動更新 |
提交表單後頁面會重新 render,資料自動更新 |
❌ Server Action + 使用 Client Component 顯示資料(透過 fetch) |
❌ 不會自動更新 |
你要手動呼叫 router.refresh() 或 revalidatePath() |
❌ Server Component 使用 fetch 且設有 cache(預設) |
❌ 不會自動更新 |
必須在 Server Action 裡手動 revalidatePath() 才會讓 Next.js 抓新資料 |
✅ 使用 cache: 'no-store' |
✅ 每次都抓新資料 |
但會失去快取效能,通常不推薦 |
revalidatePath 用法
1 2 3 4 5 6 7
| import { revalidatePath } from 'next/cache' export async function createPost(formData) { 'use server' revalidatePath('/posts') }
|