NextJS 13中如何动态更新服务端渲染组件的数据?
解答
问题1:客户端组件创建分类的首选实现方式
客户端组件确实无法直接调用Prisma(Prisma是服务端依赖,不存在于客户端环境),Next.js 13 App目录推荐的Server Actions是首选方案,比单独写API路由更简洁:
- 定义Server Action:可以单独创建一个actions文件,或者和组件放在同文件里,写一个带
'use server'指令的异步函数,内部用Prisma完成创建操作:
// app/actions.ts 'use server'; import prisma from '@/lib/prisma'; export async function createCategory(name: string) { try { const newCategory = await prisma.category.create({ data: { name }, }); return newCategory; } catch (error) { throw new Error('创建分类失败'); } }
- 客户端组件直接调用:在提交逻辑里直接调用这个函数,无需额外写fetch请求。
如果更习惯传统API路由模式,也可以创建app/api/categories/route.ts,在POST方法内用Prisma处理创建逻辑,客户端通过fetch发送POST请求,但Server Actions能减少API路由的维护成本,更适配App目录的架构。
问题2:无刷新更新服务端渲染的分类列表
可以无缝将新分类添加到现有列表,以下是几种实用方案:
方案1:Server Actions + revalidatePath(推荐)
在Server Action完成分类创建后,调用revalidatePath让Next.js重新生成对应页面的服务端数据,客户端会自动更新界面,无需整页刷新:
// app/actions.ts 'use server'; import prisma from '@/lib/prisma'; import { revalidatePath } from 'next/cache'; export async function createCategory(name: string) { try { const newCategory = await prisma.category.create({ data: { name }, }); // 重新验证展示分类列表的页面路径,比如根路径 revalidatePath('/'); return newCategory; } catch (error) { throw new Error('创建分类失败'); } }
提交后服务端会重新拉取分类数据,页面的服务端渲染部分自动更新,用户完全感知不到整页刷新。
方案2:手动更新客户端本地状态
把服务端组件获取的分类列表传递给客户端组件的状态,提交成功后直接将新分类添加到本地数组,实现即时界面更新,同时依赖revalidatePath确保服务端和客户端数据最终一致:
// app/page.tsx(服务端组件) import prisma from '@/lib/prisma'; import CategoryForm from './category-form'; // 客户端组件 export default async function Home() { const categories = await prisma.category.findMany(); return ( <div> <h1>分类列表</h1> <ul> {categories.map((cat) => ( <li key={cat.id}>{cat.name}</li> ))} </ul> <CategoryForm initialCategories={categories} /> </div> ); }
// app/category-form.tsx(客户端组件) 'use client'; import { useState } from 'react'; import { createCategory } from './actions'; type Category = { id: string; name: string }; export default function CategoryForm({ initialCategories }: { initialCategories: Category[] }) { const [categories, setCategories] = useState(initialCategories); const [name, setName] = useState(''); const [isModalOpen, setIsModalOpen] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { const newCategory = await createCategory(name); // 手动添加到本地状态,即时更新界面 setCategories([...categories, newCategory]); setName(''); setIsModalOpen(false); } catch (error) { alert('创建失败'); } }; return ( <> <button onClick={() => setIsModalOpen(true)}>添加分类</button> {isModalOpen && ( <div className="modal"> <form onSubmit={handleSubmit}> <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="输入分类名称" required /> <button type="submit">提交</button> <button type="button" onClick={() => setIsModalOpen(false)}>取消</button> </form> </div> )} </> ); }
这种方式用户能看到即时的界面反馈,同时服务端数据也会同步更新,保证一致性。
方案3:使用SWR/React Query缓存更新
如果用SWR或React Query获取分类数据,提交成功后调用mutate方法触发重新获取,实现无缝更新:
// app/category-list.tsx(客户端组件,用SWR) 'use client'; import useSWR from 'swr'; import { createCategory } from './actions'; type Category = { id: string; name: string }; const fetcher = async (): Promise<Category[]> => { const res = await fetch('/api/categories'); return res.json(); }; export default function CategoryList() { const { data: categories, mutate } = useSWR('/api/categories', fetcher); const handleAddCategory = async (name: string) => { await createCategory(name); // 触发重新获取数据,更新界面 mutate(); }; if (!categories) return <div>加载中...</div>; return ( <div> <ul> {categories.map((cat) => ( <li key={cat.id}>{cat.name}</li> ))} </ul> {/* 这里可以添加触发添加分类的模态框逻辑,调用handleAddCategory */} </div> ); }
内容的提问来源于stack exchange,提问作者AnonUser456
相关产品推荐
相关产品推荐

