ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Nuxt 状态管理实战:useState 与 SSR 友好的全局响应式状态

Nuxt 状态管理实战:useState 与 SSR 友好的全局响应式状态 Nuxt 状态管理实战useState 与 SSR 友好的全局响应式状态【免费下载链接】nuxtthe full-stack Vue framework项目地址: https://gitcode.com/GitHub_Trending/nu/nuxt本篇指南围绕 Nuxt 的状态管理方案展开讲解useState这一 SSR 友好的响应式共享状态机制、callOnce异步初始化模式、Pinia 集成方式以及全局共享状态的推荐写法。读完后你将掌握在 Nuxt 应用中安全地创建、初始化、共享和重置状态的一套完整实战方案并能从源码层面理解状态是如何在服务器渲染与客户端水合hydration之间保持一致的。一、useState一个 SSR 友好的 ref 替代方案Nuxt 提供useStatecomposable用于在组件之间创建响应式且对 SSR 友好的共享状态。它是 Vueref的替代实现它的值会在服务端渲染完成后保留到客户端水合阶段使用相同 key 的所有组件会共享同一个响应式状态数据会被序列化进 Nuxt 的 payload随 HTML 一起传输到浏览器因此客户端无需重新发起数据请求即可恢复状态。重要提示由于useState中的数据会被序列化为 JSON其中不能包含任何不可序列化的内容例如类实例、函数或 Symbol。1.1 基本用法在这个示例中我们使用一个组件本地的计数器状态。任何其他组件只要使用useState(counter)就会共享同一个响应式状态script setup langts const counter useState(counter, () Math.round(Math.random() * 1000)) /script template div Counter: {{ counter }} button clickcounter /button button clickcounter-- - /button /div /template1.2 源码解析状态存储在哪里要理解useState的 SSR 原理最直接的方式是看它的实现 packages/nuxt/src/app/composables/state.tsconst useStateKeyPrefix $s export function useStateT (...args: any): RefT { // 支持 useState(() ...) 与 useState(key, () ...) 两种签名 const autoKey typeof args[args.length - 1] string ? args.pop() : undefined if (typeof args[0] ! string) { args.unshift(autoKey) } const [_key, init] args as [string, (() T | RefT)] if (!_key || typeof _key ! string) { throw stateDiagnostics.NUXT_E7009({ key: _key }) } if (init ! undefined typeof init ! function) { throw stateDiagnostics.NUXT_E7007({ type: typeof init }) } const key useStateKeyPrefix _key const nuxtApp useNuxtApp() const state toRef(nuxtApp.payload.state, key) // Register the init function for reset support if (init) { nuxtApp._state[key] ?? { _default: init } } if (state.value undefined init) { const initialValue init() if (isRef(initialValue)) { // vue will unwrap the ref for us nuxtApp.payload.state[key] initialValue return state } state.value initialValue } return state }从源码结构看有几个关键设计状态挂在nuxtApp.payload.state上所有useState的值最终都存放在 Nuxt 应用的 payload 中payload 会随 SSR 响应序列化传输这就是服务端状态在客户端水合后仍然保留的底层机制。$s前缀每个 key 会被加上$s前缀存储如counter实际存储为$scounter。这个前缀也是后续clearNuxtState用来筛选哪些状态属于 useState的依据。key 是必填的如果缺少 key会抛出NUXT_E7009诊断错误对应 错误页 e7009init如果不是函数则抛出NUXT_E7007对应 错误页 e7007。不过当 key 缺失时Nuxt 的编译器插件 packages/nuxt/src/compiler/plugins/keyed-functions.ts 会在编译期自动为useState(() ...)这类调用注入一个基于文件与行号生成的唯一 key因此不传 key 也能正常工作官方 API 文档说明不提供 key 时会自动生成一个对该文件与行号唯一的 key。init支持返回RefuseState(key, () shallowRef({...}))是官方支持的性能优化写法——当状态包含大型对象或数组、且不需要深度响应时配合shallowRef可以提升性能。源码中对isRef(initialValue)分支做了专门的解包处理。init 函数被注册用于重置nuxtApp._state[key] ?? { _default: init }这一行把初始化函数记录下来供clearNuxtState的reset选项在清除状态后重新执行默认值。仓库中的单元测试 test/nuxt/composables.test.ts 验证了这些行为不传 key 时自动使用 autoKey、状态以$s前缀注册到payload.state、返回的shallowRef会被解包为普通 ref、相同 key 始终返回同一个响应式引用。::noteuseState是被编译器转换的保留函数名不要把自己的函数命名为useState。 ::二、最佳实践状态定义位置::warning永远不要在script setup或setup()函数之外定义const state ref()。 例如执行export myState ref({})会导致状态在服务端跨请求共享并可能引发内存泄漏。 ::::tip 正确做法是写成const useX () useState(x)这样的 composable 函数。 ::原因在于模块顶层的ref在 Node.js 中是进程级单例——服务端每个请求处理完后该模块不会销毁下一个请求会继续读写同一份数据而useState把状态挂在每个请求独立的nuxtApp.payload上天然实现了请求间隔离同时又能在客户端水合时恢复。三、初始化状态用 callOnce 做服务端数据填充大多数时候你会希望用异步数据来初始化状态。可以在app.vue组件中结合callOnce工具函数实现script setup langts const websiteConfig useState(config) await callOnce(async () { websiteConfig.value await $fetch(https://my-cms.com/api/website-config) }) /script::tip 这与 Nuxt 2 中的nuxtServerInitaction 类似允许在渲染页面前先在服务端填充 store 的初始状态。 ::callOnce的本质是SSR 友好的一次性调用。从实现 packages/nuxt/src/app/composables/once.ts 可以看到它的工作机制以key为去重单位执行成功后 key 会被加入nuxtApp.payload.once集合该集合同样随 payload 序列化传输因此客户端水合后不会重复执行正在飞行中的 Promise 缓存在nuxtApp._once上多个并发调用会等待同一个 Promise失败时删除缓存并向上抛出支持mode: navigation选项通过路由守卫在下次导航的beforeResolve时删除 key允许在导航后重新执行开发模式下监听 Vite 的vite:beforeUpdate/vite:afterUpdate在 HMR 更新时允许重新执行避免热更新卡住初始化逻辑。四、使用 Pinia如果需要更完整的状态管理方案模块化 store、DevTools 支持等可以借助 Nuxt 的 Pinia 模块创建全局 store 并在整个应用中复用。::important 请确保已通过npx nuxt module add pinia安装 Pinia 模块或按照 Nuxt 官方 Pinia 模块的文档完成安装。 ::export const useWebsiteStore defineStore(websiteStore, { state: () ({ name: , description: , }), actions: { async fetch () { const infos await $fetch(https://api.nuxt.com/modules/pinia) this.name infos.name this.description infos.description }, }, })script setup langts const website useWebsiteStore() await callOnce(website.fetch) /script template main h1{{ website.name }}/h1 p{{ website.description }}/p /main /template注意这里同样用callOnce包裹website.fetch保证数据请求在服务端与客户端水合之间只执行一次——这正是 Pinia 在 SSR 场景下配合 Nuxt 的标准姿势。五、进阶用法全局 locale 示例下面的示例展示了一个典型的跨请求/跨端一致的状态设计根据服务器端的Accept-Language请求头或客户端的navigator.language推断默认语言并用useState让所有组件共享import type { Ref } from vue export const useLocale () { return useStatestring(locale, () useDefaultLocale().value) } export const useDefaultLocale (fallback en-US) { const locale ref(fallback) if (import.meta.server) { const reqLocale useRequestHeaders()[accept-language]?.split(,)[0] if (reqLocale) { locale.value reqLocale } } else if (import.meta.client) { const navLang navigator.language if (navLang) { locale.value navLang } } return locale } export const useLocales () { const locale useLocale() const locales ref([ en-US, en-GB, // ..., ja-JP-u-ca-japanese, ]) if (!locales.value.includes(locale.value)) { locales.value.unshift(locale.value) } return locales } export const useLocaleDate (date: RefDate | Date, locale useLocale()) { return computed(() new Intl.DateTimeFormat(locale.value, { dateStyle: full }).format(unref(date))) }script setup langts const locales useLocales() const locale useLocale() const date useLocaleDate(new Date(2016-10-26)) /script template div h1Nuxt birthday/h1 p{{ date }}/p label forlocale-chooserPreview a different locale/label select idlocale-chooser v-modellocale option v-forloc of locales :keyloc :valueloc {{ loc }} /option /select /div /template这个例子的要点useDefaultLocale只是普通的响应式逻辑每次调用都返回新的ref它负责猜默认值useLocale把猜测结果固定到useState(locale, ...)中——一旦初始化后续所有组件拿到的都是同一个值且该值随 SSR 传输到客户端避免服务端识别的语言和客户端渲染不一致造成的 UI 闪烁useLocales、useLocaleDate都是基于共享locale状态派生的普通 composable。六、共享状态自动导入的 composable借助 自动导入的 composables可以定义全局、类型安全的状态并在整个应用中直接导入使用export const useColor () useStatestring(color, () pink)script setup langts const color useColor() // Same as useState(color) /script template pCurrent color: {{ color }}/p /template把useState包在一层 composable 里的好处类型由泛型T显式约束初始化逻辑集中在一处维护调用点语义更清晰useColor()而非useState(color)。七、clearNuxtState全局失效缓存状态如果需要全局性地使已缓存的状态失效例如用户切换账号、登出使用clearNuxtState工具函数。从实现 packages/nuxt/src/app/composables/state.ts 可以看到它的完整能力export function clearNuxtState ( keys?: string | string[] | ((key: string) boolean), opts?: ClearNuxtStateOptions, ): void不传keys清除所有useState状态通过$s前缀筛选传字符串/数组只清除指定 key传函数按谓词过滤要清除的 keyreset选项默认值来自 Nuxt 配置中的experimental.defaults.useState.resetOnClear为true时状态会被重置回useState的init函数提供的初始值利用第五节源码中注册在nuxtApp._state上的 init 函数而不是简单删除。此外Nuxt 的 chunk 错误恢复机制也会依赖同一套 payload 状态当客户端 chunk 加载失败触发整页重载时packages/nuxt/src/app/composables/chunk.ts 会把payload.state存入sessionStorage随后 restore-state 插件 在app:mounted时将其写回nuxtApp.payload.state保证刷新后用户状态不丢失。八、第三方状态管理库Nuxt曾经依赖Vuex 提供全局状态管理如果你正从 Nuxt 2 迁移过来请参阅 迁移指南 中关于 Vuex 的部分。Nuxt 对状态管理不做强制选型你可以按需选择最合适的方案。生态中与主流状态管理库的集成包括Pinia— Vue 官方推荐的 store 库Nuxt 生态中最常用的选择Harlem— 不可变immutable全局状态管理XState— 状态机state machine思路附带可视化与测试状态逻辑的工具。九、常见陷阱与排查陷阱表现建议在script setup/setup()外定义ref服务端状态跨请求共享、内存泄漏改为const useX () useState(x)状态中存入类实例、函数、Symbol序列化报错Cannot stringify arbitrary non-POJOs只存可 JSON 序列化数据确需存类实例时可参考useNuxtApp文档中的definePayloadPlugin为自定义类添加序列化/反序列化器把init写成非函数值抛出NUXT_E7007错误init必须是无参函数惰性求值忘记传 key 且依赖编译器自动 key代码重构、行号变化后 key 漂移显式传入稳定的字符串 key小结useState是 Nuxt 内置的、SSR 友好的全局共享状态方案值保存在nuxtApp.payload.state带$s前缀随服务端渲染序列化传输到客户端水合后继续可用异步初始化放在app.vue中并用callOnce去重等价于 Nuxt 2 的nuxtServerInit遵循composable 包装 显式 key的最佳实践配合自动导入即可构建类型安全的全局共享状态需要更复杂的场景模块化 store、DevTools时选择 Pinia 等第三方方案状态失效/重置用clearNuxtState处理。【免费下载链接】nuxtthe full-stack Vue framework项目地址: https://gitcode.com/GitHub_Trending/nu/nuxt创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表