Nuxt4阶段三数据获取与状态管理学习目标熟练使用useFetch/useAsyncData/$fetch理解 Nuxt 4 Singleton 数据层掌握useState与 Pinia。预计时间1–2 周前置完成阶段二博客骨架。本阶段产出Todo App列表、新增、刷新 博客列表改为真实请求。1. 三种取数方式怎么选API场景特点useFetch页面/组件里拉接口最常用封装了$fetch 缓存 key SSRuseAsyncData需要自定义 async 逻辑更灵活可包任意 Promise$fetch事件回调里发请求点击提交等不自动绑定组件生命周期与 SSR 数据传递经验法则首屏需要的数据→useFetch/useAsyncData带await用户点击后才请求→$fetch同一份数据多处用→ 相同key让 Nuxt 4 共享见下文2. 先写一个本地 API方便练习创建server/api/posts.get.tsexportdefaultdefineEventHandler((){return[{id:1,title:什么是 Nuxt 4,excerpt:从零认识 Nuxt 4,body:Nuxt 是 Vue 全栈框架……},{id:2,title:文件路由入门,excerpt:pages 如何变成 URL,body:pages 目录决定 URL……},{id:3,title:数据获取指南,excerpt:useFetch 怎么用,body:Nuxt 4 数据层更聪明……},]})创建server/api/posts/[id].get.tsexportdefaultdefineEventHandler((event){constidgetRouterParam(event,id)constall[{id:1,title:什么是 Nuxt 4,body:Nuxt 是 Vue 全栈框架……},{id:2,title:文件路由入门,body:pages 目录决定 URL……},{id:3,title:数据获取指南,body:Nuxt 4 数据层更聪明……},]constpostall.find(pp.idid)if(!post){throwcreateError({statusCode:404,statusMessage:Not Found})}returnpost})开发服务器下访问http://localhost:3000/api/postshttp://localhost:3000/api/posts/1阶段四会系统讲 server这里先当「假后端」。3.useFetch基础改造app/pages/blog/index.vuescriptsetuplangtsconst{data:posts,pending,error,refresh}awaituseFetch(/api/posts,{key:posts-list,})useSeoMeta({title:博客列表})/scripttemplatesectiondivclasstoolbarh1博客/h1button:disabledpendingclick() refresh()刷新/button/divpv-ifpending加载中…/ppv-else-iferror加载失败{{ error.message }}/pBlogPostCardv-elsev-forp in posts:keyp.id:idp.id:titlep.title:excerptp.excerpt//section/template返回值常用字段字段含义data响应数据Refpending是否请求中error错误对象statusidle | pending | success | errorrefresh重新请求clear清理该 key 的数据详情页scriptsetuplangtsconstrouteuseRoute()constidcomputed(()route.params.idasstring)const{data:post,error}awaituseFetch(()/api/posts/${id.value},{key:computed(()post-${id.value}),})if(error.value){throwcreateError({statusCode:404,statusMessage:文章不存在})}useSeoMeta({title:()post.value?.title??文章,})/scripttemplatearticlev-ifpostNuxtLinkto/blog← 返回/NuxtLinkh1{{ post.title }}/h1p{{ post.body }}/p/article/template注意 URL 写成函数() ...配合响应式key切换文章时会重新请求。4. Nuxt 4 数据层核心变化必懂4.1 Singleton相同 key 共享一份数据多个组件写awaituseFetch(/api/posts,{key:posts-list})在 Nuxt 4 里它们共享同一份data/error/status不会重复打接口。好处Header、列表、侧边栏同时用同一数据更一致性能更好一处refresh()相关组件一起更新4.2 响应式 keyconstpageref(1)const{data}awaituseFetch(/api/posts,{key:computed(()posts-page-${page.value}),query:{page},})key或query变化时自动重新获取。4.3 自动清理当最后一个使用该 key 的组件卸载后Nuxt 会清理缓存避免内存常驻。4.4getCachedData进阶可精细控制「何时用缓存、何时强制请求」const{data}awaituseFetch(/api/posts,{key:posts-list,getCachedData(key,nuxtApp,ctx){// ctx.cause 可区分 refresh、watch 等原因returnnuxtApp.payload.data[key]||nuxtApp.static.data[key]},})入门阶段知道「能控制缓存」即可不必死记。4.5createUseFetchNuxt 4.4封装带默认配置的 fetch如统一 baseURL、headers// app/composables/useApi.tsexportconstuseApicreateUseFetch((baseURL)({baseURL:/api,// onRequest({ options }) { ... }}))具体 API 以你安装的 Nuxt 小版本文档为准没有该 API 时自己包一层useFetch也可以。5.useAsyncData与$fetch5.1useAsyncData适合「不是单纯一个 URL」的逻辑const{data}awaituseAsyncData(dashboard,async(){const[posts,stats]awaitPromise.all([$fetch(/api/posts),$fetch(/api/stats),])return{posts,stats}})useFetch(url)本质上是useAsyncData$fetch(url)的语法糖。5.2$fetch用在事件里scriptsetuplangtsconsttitleref()asyncfunctioncreatePost(){await$fetch(/api/posts,{method:POST,body:{title:title.value},})title.valueawaitrefreshNuxtData(posts-list)// 刷新列表缓存}/scriptrefreshNuxtData(posts-list)按 key 刷新不传参则刷新全部。6. 练习项目Todo App6.1 APIserver/api/todos.get.tsconsttodos[{id:1,text:学习 useFetch,done:true},{id:2,text:学习 Pinia,done:false},]exportdefaultdefineEventHandler(()todos)注意上面写在模块顶层的数组在热重载/多实例下仅适合学习演示。阶段四会改成更合理的存储。server/api/todos.post.tsexportdefaultdefineEventHandler(async(event){constbodyawaitreadBody{text:string}(event)if(!body?.text?.trim()){throwcreateError({statusCode:400,statusMessage:text required})}// 演示返回新 todo真实项目写入 DBreturn{id:Date.now(),text:body.text.trim(),done:false}})6.2 页面app/pages/todos.vuescriptsetuplangtstype Todo{id:number;text:string;done:boolean}const{data:todos,pending,refresh}awaituseFetchTodo[](/api/todos,{key:todos,})consttextref()constsubmittingref(false)asyncfunctionaddTodo(){if(!text.value.trim())returnsubmitting.valuetruetry{constcreatedawait$fetchTodo(/api/todos,{method:POST,body:{text:text.value},})// 乐观更新或直接 refreshtodos.value[...(todos.value||[]),created]text.value}finally{submitting.valuefalse}}useSeoMeta({title:Todos})/scripttemplatesectionh1Todos/h1formsubmit.preventaddTodoinputv-modeltextplaceholder新任务/button:disabledsubmitting添加/button/formpv-ifpending加载中…/pulv-elseliv-fort in todos:keyt.idlabelinputv-modelt.donetypecheckbox/{{ t.text }}/label/li/ulbuttonclick() refresh()重新拉取/button/section/template导航里加上/todos链接。7. 状态管理useStatevs Pinia7.1 简单状态继续用useStateexportfunctionuseAuthUser(){returnuseState{name:string}|null(auth-user,()null)}适合主题、简单用户信息、跨页面 UI 状态。7.2 复杂业务上 Pinia安装pnpmaddpinia pinia/nuxtnuxt.config.tsexportdefaultdefineNuxtConfig({modules:[pinia/nuxt],})app/stores/todo.tsimport{defineStore}frompiniaexportconstuseTodoStoredefineStore(todo,(){constitemsref{id:number;text:string;done:boolean}[]([])constloadingref(false)asyncfunctionfetchAll(){loading.valuetruetry{items.valueawait$fetch(/api/todos)}finally{loading.valuefalse}}asyncfunctionadd(text:string){constcreatedawait$fetch(/api/todos,{method:POST,body:{text},})items.value.push(created)}return{items,loading,fetchAll,add}})页面scriptsetuplangtsconststoreuseTodoStore()onMounted((){if(!store.items.length)store.fetchAll()})/scriptSSR 场景首屏数据仍优先useFetchPinia 更适合客户端交互状态与跨页业务。也可配合 Pinia 的 SSR 水合方案进阶再查。选择建议需求方案与请求绑定的服务端数据useFetch少量全局 UI 状态useState复杂、多模块业务状态Pinia8. 错误与加载的最佳实践scriptsetuplangtsconst{data,status,error,refresh}awaituseFetch(/api/posts,{key:posts-list,lazy:false,// 默认true 则不阻塞导航server:true,// 默认在服务端也请求})/scripttemplatedivv-ifstatus pendingLoading…/divdivv-else-ifstatus error出错了{{ error?.message }}buttonclick() refresh()重试/button/divdivv-else…展示 data…/div/template常用选项选项作用lazy: true不阻塞路由先出页面再填数据server: false仅客户端请求default: () []data初始默认值watch: [page]依赖变化时重请求immediate: false先不请求稍后手动execute()9. 本阶段验收清单/api/posts、/api/posts/:id可用博客列表与详情改为useFetch去掉硬编码假数据理解并实际用了命名keyTodo 页能列表展示 $fetch新增加分接入 Pinia store加分两个组件同时useFetch同一 key在 Network 面板确认只请求一次10. 常见坑在onClick里await useFetch不要。交互里用$fetch或提前useFetchrefresh。忘记key数据错乱动态页面务必用包含 id 的 key。模块顶层存数组当数据库仅演示生产用 DB 或至少用 Nitro 存储阶段四。客户端拿到的data是 null检查是否在 setup 顶层await useFetch或是否server: false且未处理 pending。11. 阶段总结你已经掌握useFetch/useAsyncData/$fetch分工Nuxt 4 同 key 共享与refreshNuxtDataTodo 小应用数据流useState与 Pinia 的选型下一篇阶段四 —— Server 目录、API 路由约定、中间件、Cookie、对接数据库。参考链接Data FetchinguseFetchuseAsyncDataPiniaAnnouncing Nuxt 4