NextJS 14 App Router中RSC与RCC的类型安全第三方API获取方案
Next.js 14中RSC与RCC类型安全调用第三方API并处理数据更新的方案
一、实现类型安全的数据获取
1. 定义API返回数据的类型
先明确第三方API返回的用户结构,提前定义对应TypeScript类型:
// 单个用户的类型(根据实际API返回字段调整) export type User = { id: string; name: string; email: string; // 补充其他字段,如createdAt、role等 }; // API返回的用户列表类型 export type GetUsersResponse = User[];
2. 封装类型安全的服务器端API调用
把fetch逻辑封装成单独的服务器端函数,集中处理授权、类型校验和错误:
// app/api/users/server-actions.ts import { getSession } from "@/lib/auth"; // 替换为你的会话获取逻辑 import { z } from "zod"; // 可选:用zod做严格的schema校验 // 可选:用zod定义schema,确保返回数据符合预期 const UserSchema = z.object({ id: z.string(), name: z.string(), email: z.string(), }); const GetUsersResponseSchema = z.array(UserSchema); export async function fetchUsers(): Promise<GetUsersResponse> { const session = await getSession(); if (!session?.user?.access_token) { throw new Error("未授权,无法获取用户数据"); } const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/users/all`, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${session.user.access_token}`, }, next: { tags: ["users"] }, // 标记缓存标签,用于后续重新验证 }); if (!res.ok) { const errorMsg = await res.json().catch(() => res.statusText); throw new Error(`获取用户失败:${errorMsg}`); } const rawData = await res.json(); // 用zod校验并转换类型,不符合则抛出错误 return GetUsersResponseSchema.parse(rawData); // 若不用zod,也可以用类型断言:return rawData as GetUsersResponse; }
3. 在RSC页面中调用封装函数
修改Page.tsx,此时users会自动获得正确的类型,不再是any:
// app/users/Page.tsx import { fetchUsers } from "@/app/api/users/server-actions"; import Table from "./Table"; type SearchParamsType = { searchParams: { [key: string]: string | string[] | undefined; }; }; export async function Page({ searchParams }: SearchParamsType) { try { const users = await fetchUsers(); return <Table users={users} />; } catch (error) { return <div>加载用户失败:{(error as Error).message}</div>; } }
二、处理RCC修改数据后的重新获取
由于Table是客户端组件,无法直接触发服务器端的缓存更新,我们可以通过Server Action + 缓存标签重验证实现:
1. 创建触发重验证的Server Action
// app/api/users/server-actions.ts "use server"; // 标记为Server Action,只能在服务器端执行 import { revalidateTag } from "next/cache"; export async function revalidateUsers() { // 重验证标记为"users"的缓存数据,触发RSC重新获取用户列表 await revalidateTag("users"); }
2. 在客户端组件Table中调用该Action
当用户完成数据修改(编辑/删除等)后,调用revalidateUsers触发数据更新:
// app/users/Table.tsx "use client"; // 标记为客户端组件 import { User } from "@/app/api/users/server-actions"; import { revalidateUsers } from "@/app/api/users/server-actions"; type TableProps = { users: User[]; }; export default function Table({ users }: TableProps) { const handleUserEdit = async (userId: string, newName: string) => { // 执行客户端的修改请求 const updateRes = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/users/${userId}`, { method: "PUT", headers: { "Content-Type": "application/json", // 客户端获取token需使用安全方式,比如从auth库的客户端会话或localStorage(注意XSS风险) Authorization: `Bearer ${localStorage.getItem("access_token")}`, }, body: JSON.stringify({ name: newName }), }); if (updateRes.ok) { // 修改成功后触发缓存重验证,让RSC重新拉取最新数据 await revalidateUsers(); } }; return ( <table> <thead> <tr> <th>姓名</th> <th>邮箱</th> <th>操作</th> </tr> </thead> <tbody> {users.map(user => ( <tr key={user.id}> <td>{user.name}</td> <td>{user.email}</td> <td> <button onClick={() => handleUserEdit(user.id, "新名字")}>编辑</button> </td> </tr> ))} </tbody> </table> ); }
额外注意事项
- 客户端授权:避免在客户端直接硬编码或暴露敏感token,建议使用NextAuth等库的客户端会话方法获取授权信息。
- 错误处理:可以在Server Action和客户端请求中添加更细致的错误捕获,比如网络异常、权限不足等场景的提示。
内容的提问来源于stack exchange,提问作者Bert Van Hecke
相关产品推荐
相关产品推荐

