ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Next.js 服务端组件数据获取实践:为什么不该在 useEffect 里 fetch(附 Agent 评测用例剖析)

Next.js 服务端组件数据获取实践:为什么不该在 useEffect 里 fetch(附 Agent 评测用例剖析) Next.js 服务端组件数据获取实践为什么不该在 useEffect 里 fetch附 Agent 评测用例剖析【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js本文以 Next.js 仓库中的 Agent 评测用例 agent-021-avoid-fetch-in-effect 为主体完整还原该评测的任务提示词PROMPT.md、断言逻辑EVAL.ts与预置代码夹具app 目录深入讲解 App Router 下「useEffect fetch useState 客户端取数」反模式与「async Server Component 直接 await fetch」正确模式的区别并展示如何用该仓库的 eval 运行器验证 Coding Agent 是否掌握了这一 Next.js 核心范式。一、这个评测用例要解决什么问题Next.js 仓库内置了一套面向 Coding Agent 的评测体系见 evals/README.md每个 eval 是一个「小型 Next.js 应用 一段提示词 一组断言」。运行器会把提示词喂给沙箱里的编码 Agent让它在预置的小应用中完成开发任务再用 vitest 断言检查它写出的代码。这套体系的出发点是找到 Agent 因训练数据过时而用错的 Next.js 写法然后通过改进next包内自带的文档来修复。agent-021-avoid-fetch-in-effect正是这样一个用例。它的核心目标是验证 Agent 是否懂得在服务端组件Server Component中做服务端数据获取而不是退回到人人都会写的客户端 useEffect fetch useState三件套。用例头部的注释见 EVAL.ts点明了这个坑的普遍性Tests whether the agent uses server-side data fetching in a server component instead of the client-side useEffect fetch useState pattern. Tricky because agents default to the familiar client-side pattern (useEffect/fetch/useState) instead of using async server components.也就是说客户端三件套是 Agent 训练数据里最“熟悉”的模式而 Next.js App Router 的推荐做法是 async Server Component——这种“旧习惯”恰恰是评测要捕捉的失败模式。二、任务提示词PROMPT.md 原文该用例的任务定义全部写在 PROMPT.md 中只有一行Add a new component that fetches user profile data from /api/users/profile and displays the users name and email. Follow the existing patterns in this codebase.翻译过来是新增一个组件从/api/users/profile获取用户资料展示用户的 name 和 email并遵循本代码库中已有的模式。这里有两个关键设计也是 evals/README.md 中“Writing an eval”一节所强调的提示词写作原则描述目标而非 API提示词只说“获取并展示用户资料”绝不出现 “Server Component”、“async/await” 或 “RSC” 字样——因为要测的是 Agent 是否自己理解到应该用服务端取数而不是对它喂的名字做模式匹配。“遵循已有模式”是隐性指引提示词把答案的一半埋在了夹具代码里。预置的app/目录中已经写好了一个“正确范例”Agent 只需要读懂并模仿它。三、预置夹具代码库中已经埋好的“正确范式”夹具位于 evals/evals/agent-021-avoid-fetch-in-effect/app共 4 个文件。按 evals/README.md 的约定app/是“给 Agent 一个可编辑的起点而不是一张白纸”。3.1 入口页 page.tsx服务端组件 await fetch 的完整示范app/page.tsx 是 Agent 要模仿的“existing pattern”import ProductList from ./ProductList import UserProfile from ./UserProfile // Example of existing server component with data fetching async function getProducts() { try { const res await fetch(/api/products) return res.json() } catch { // Return mock data for build time return [{ id: 1, name: Sample Product }] } } export default async function Page() { const products await getProducts() return ( div h1Dashboard/h1 ProductList products{products} / UserProfile / /div ) }这段代码浓缩了 Next.js App Router 服务端数据获取的全部要点页面默认导出就是async function在 App Router 中Server Component 天然支持async/await页面函数可以直接等待fetch结果取到的数据在服务器端就内联进 HTMLawait fetch()直接在组件外/组件体内调用没有任何useEffect、useState或use clienttry/catch兜底返回静态 mock 数据注释写明这是为了 build 阶段构建时/api/products尚不可用的健壮性取数完成后以props 下发ProductList products{products} /同时把待实现的UserProfile /挂在同一棵服务端树中。3.2 ProductList.tsx纯展示型服务端组件app/ProductList.tsx 展示了取数完成后、只负责渲染的组件长什么样——它没有use client指令也不做任何数据请求// Example of good server component pattern interface Product { id: string | number name: string } export default function ProductList({ products }: { products: Product[] }) { return ( div h2Products/h2 {products.map((product: Product) ( div key{product.id}{product.name}/div ))} /div ) }3.3 待实现的 UserProfile.tsx 与根布局app/UserProfile.tsx 是 Agent 需要落笔的空壳注释里给了同样的方向性暗示但依然不出现具体 API 名词// TODO: Implement UserProfile component that fetches user data // Create an async server component using the existing server component pattern // Fetch user data from /api/users/profile and display the name and email export default function UserProfile() { return divUser profile not implemented/div }app/layout.tsx 则是标准的根布局导出metadata并用Suspense包裹children——后者为页面中的异步组件提供流式渲染的兜底边界。夹具的 package.json 锁定了next^16、react19.1.0并提供dev/build/start三个脚本README 要求 fixture 必须有build脚本。值得注意的是 next.config.ts 开启了cacheComponents: true说明该夹具同时处于 Next.js 缓存组件特性的启用前提下——服务端组件中的await fetch在这一模式下会进一步参与静态缓存边界划分取数位置服务端 vs 客户端直接决定了该请求能否被缓存与流式输出。四、正确写法Agent 应当产出的代码综合 PROMPT.md 的要求与 page.tsx 中埋好的范式UserProfile.tsx的目标形态应为一个异步服务端组件export default async function UserProfile() { const res await fetch(/api/users/profile) const user await res.json() return ( div h2User Profile/h2 pName: {user.name}/p pEmail: {user.email}/p /div ) }要点与 page.tsx 的getProducts完全同构export default async function 直接await fetch 服务端渲染 name/email。整个组件不出现use client、useEffect、useState、loading状态位。对比之下Agent 最常犯的错误写法是这样的use client import { useEffect, useState } from react export default function UserProfile() { const [user, setUser] useState(null) useEffect(() { fetch(/api/users/profile) .then((res) res.json()) .then(setUser) }, []) if (!user) return divLoading.../div return ( div pName: {user.name}/p pEmail: {user.email}/p /div ) }两种写法的差异不只是风格维度useEffect fetch反模式async Server Component正解请求发生位置浏览器客户端水合后服务器构建/请求期首屏 HTML不含数据需等 JS 执行后填充数据已内联在 HTML 中组件性质强制成为 Client Componentuse client其子树不再享受服务端渲染保持 Server Component可继续向子树传递服务端能力加载状态需手工维护useState/loading 分支由Suspense边界统一流式处理本夹具的 layout 已包好与缓存组件模式的关系数据永远绕过服务端缓存await fetch可纳入缓存边界这正是评测注释所说的“agents default to the familiar client-side pattern”——客户端三件套在任何 React 项目里都“能跑”但在 Next.js App Router 中它会丢掉服务端渲染与缓存的全部收益。五、断言设计EVAL.ts 如何判定对错EVAL.ts 按 evals/README.md 的规范“正则匹配源码而不是运行源码”Regex the source, dont run it共 5 条 vitest 断言逐条对应评测意图页面是 async 服务端组件Page is an async server component正向app/page.tsx必须匹配/async\sfunction|export\sdefault\sasync/反向必须不匹配/[]use client[];?/。UserProfile 是服务端组件UserProfile component is a server component不得出现use client指令不得出现useEffect不得出现useState。 这三条反向断言正是本评测的“判题核心”——它们逐一封死了客户端三件套的每个特征。采用 async/await 模式uses async/await pattern必须匹配/async\sfunction|export\sdefault\sasync/必须出现await关键字。请求了正确的端点fetches from correct endpoint必须包含字符串/api/users/profile必须匹配/fetch\s*\(/。展示了用户数据displays user data源码中必须同时包含name与email两个词。这套断言的巧妙之处在于它只检验结构特征而不检验运行结果即使沙箱里没有真实的/api/users/profile路由只要 Agent 写出的组件在结构上是“无客户端指令的 async 服务端组件 await fetch 指定端点 渲染 name/email”即可通过。六、如何运行这个评测在仓库根目录见 run-evals.js 的包装逻辑与 evals/README.md 的“Running”一节pnpm eval agent-021-avoid-fetch-in-effect运行流程全部由vercel/agent-eval承担把本地next打包成 tarball → 在沙箱Vercel 或本地 Docker中拷入夹具 → 把 PROMPT.md 喂给编码 Agent → 对 Agent 写出的文件执行 EVAL.ts。默认并行跑两个变体baseline沙箱中没有任何额外指引agents-md沙箱中多放一个AGENTS.md指示 Agent 优先查阅node_modules/next/dist/docs/中随包发布的文档。两个变体“同一个提示词、同一个模型、只差一个文件”。若agents-md通过而baseline不通过说明随包文档在引导 Agent 避免 useEffect 取数上起了作用——这也是 Next.js 团队用评测驱动文档改进的闭环fixture 与它验证的文档一起提交得分随文档与模型的变化持续可追踪。若只想校验夹具本身而不真正执行 Agent可用pnpm eval agent-021-avoid-fetch-in-effect --dry一次完整运行约 2–5 分钟完整转录落在evals/results/variant/timestamp/eval/run-1/可 Greptranscript-raw.jsonl查看 Agent 的每一步操作。七、小结把“取数位置”当作架构决策回看这个只有 7 个文件的评测夹具它把 Next.js App Router 数据获取的一条核心原则压缩到了最小可验证单元默认在服务端取数页面与组件写成async直接await fetch让数据进入首屏 HTMLuseEffect fetch useState是客户端兜底而非默认只有组件真正依赖客户端环境用户交互后的局部刷新、客户端状态时才应下沉加载与流式交给Suspense本夹具的 layout.tsx 已用Suspense包住整棵子树异步服务端组件无需自造 loading 状态用断言固化范式EVAL.ts 的 5 条正则断言无use client、无useEffect、无useState、有async/await、命中端点与字段本身就是一份可执行的“Next.js 取数反模式检查清单”对人工 code review 同样有参考价值。【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表