onMounted钩子访问Pinia store时状态值为null的问题排查
问题:onMounted钩子中访问Pinia store时状态全为null
我遇到一个问题:当通过onMounted钩子访问Pinia store时,其所有状态属性均为null。调用栈为:组件(onMounted)→组合式函数→Pinia store。
组件代码
onMounted(async () => { const resp = await $authFetch(config.functionsBaseUrl + 'v1/account/settings', { method: 'GET' }).catch(e => { toast.show({ type: 'error', title: 'Error', content: e.data }); return null; }) if (resp === null) return; userSetings.value = resp; })
组合式函数代码
export async function $authFetch<T>( request: Parameters<typeof $fetch<T>>[0], opts?: Parameters<typeof $fetch<T>>[1], ) { const auth = useAuth(useNuxtApp().$pinia); const shouldRefreshToken = () => { const currentTime = Math.floor(Date.now() / 1000); // 计算过期时间与当前时间的差值 console.log('calculating', auth.tokenExpiration); const timeDifference = auth.tokenExpiration - currentTime; console.log('time diff', timeDifference); // 检查token是否在5分钟内过期 return timeDifference <= 300; } console.log('authFetch', auth, auth.tokenExpiration, shouldRefreshToken()); if (auth.tokenExpiration && shouldRefreshToken()) { const url = useRuntimeConfig().public.functionsBaseUrl + 'v1/auth/refresh'; console.log('refreshing', url); const resp = await $fetch(url, { method: 'POST', body: { refreshToken: auth.refreshToken, idToken: auth.token }, headers: { Authorization: `Bearer ${auth.token}` } }).catch(e => { if (e.data === 'Unauthorized') { // 刷新token过期,登出用户 auth.$reset(); const cookie = useCookie('auth'); cookie.value = null; } }); auth.token = resp.Token; auth.tokenExpiration = resp.Expiration; } return $fetch<T>(request, { ...opts, headers: { Authorization: `Bearer ${auth.token}`, ...opts?.headers, }, }); }
Store代码
export const useAuth = defineStore('auth', { state: () => ({ token: null, refreshToken: null, email: null, userId: null, firstName: null, lastName: null, organizationId: null, organizationName: null, organizationIndustry: null, tokenExpiration: null }), actions: { setLoginPayload(loginResponse) { this.token = loginResponse.Token; this.refreshToken = loginResponse.RefreshToken; this.email = loginResponse.Email; this.userId = loginResponse.UserId; this.firstName = loginResponse.FirstName; this.lastName = loginResponse.LastName; this.organizationId = loginResponse.OrganizationId; this.organizationName = loginResponse.CompanyName; this.organizationIndustry = loginResponse.Industry; this.tokenExpiration = loginResponse.Expiration; console.log('set token expiration', loginResponse.Expiration); // 存入cookie const cookie = useCookie('auth'); cookie.value = loginResponse; } } })
现象
如果将onMounted内的代码包裹在1秒延迟中,store的值就不再为空。我未找到任何说明Pinia需要准备时间或可等待其就绪的文档。
问题
是否有明显原因导致在onMounted中调用时store为空?
编辑1:补充设置store的代码
登录后(login.vue):
const login = async () => { loggingIn.value = true; var url = useRuntimeConfig().public.functionsBaseUrl + "v1/auth/login"; var response = await $fetch(url, { method: 'POST', body: { email: email.value, password: password.value } }).catch(error => { console.log(error); toast.show({ title: 'Login Error', content: error.data, type: 'error', timeout: 5000 }) loggingIn.value = false; }); if (response) { auth.setLoginPayload(response); toast.show({ content: `Welcome ${auth.firstName}!`, type: 'success', title: 'Success' }); const router = useRouter(); setTimeout(() => { router.push('/documents'); }, 1000); } };
用户刷新浏览器时(app.vue):
onMounted(() => { const jsonAuth = useCookie('auth'); console.log('jsonauth', jsonAuth.value); // 保持用户登录状态 // TODO:处理刷新token过期的情况,此时刷新机制失效,需要跳转登录页 if (jsonAuth.value) { const auth = useAuth(); auth.setLoginPayload(jsonAuth.value); } })
解答
核心原因是页面组件的onMounted执行时机早于根组件app.vue的onMounted,导致组件调用$authFetch时,app.vue中从cookie恢复store状态的逻辑还未完成。
在Nuxt中,页面组件的onMounted会优先于app.vue的onMounted执行。用户刷新页面时的流程是:
- 页面组件的onMounted触发,调用
$authFetch $authFetch读取Pinia store,此时store还是初始的null状态- 之后app.vue的onMounted才执行,从cookie中恢复store数据
添加1秒延迟能解决问题,本质是让组件的请求等待app.vue的store初始化完成。
修复方案
方案1:改用Nuxt的useAsyncData替代组件内的onMounted请求
Nuxt的useAsyncData会在页面渲染前处理异步逻辑,且能确保依赖的store状态已初始化:
const { data: userSettings } = useAsyncData('user-settings', async () => { try { return await $authFetch(config.functionsBaseUrl + 'v1/account/settings', { method: 'GET' }) } catch (e) { toast.show({ type: 'error', title: 'Error', content: e.data }); return null; } })
方案2:确保store初始化完成后再执行组件逻辑
- 在auth store中添加初始化状态和方法:
export const useAuth = defineStore('auth', { state: () => ({ // ...原有状态 isInitialized: false }), actions: { // ...原有actions async initFromCookie() { const jsonAuth = useCookie('auth'); if (jsonAuth.value) { this.setLoginPayload(jsonAuth.value); } this.isInitialized = true; } } })
- 在app.vue的onMounted中调用初始化方法:
onMounted(async () => { const auth = useAuth(); await auth.initFromCookie(); })
- 在组件中等待store初始化完成后再请求:
onMounted(async () => { const auth = useAuth(); // 等待store初始化完成 while (!auth.isInitialized) { await new Promise(resolve => setTimeout(resolve, 50)); } // 执行请求逻辑 const resp = await $authFetch(config.functionsBaseUrl + 'v1/account/settings', { method: 'GET' }).catch(e => { toast.show({ type: 'error', title: 'Error', content: e.data }); return null; }) if (resp !== null) { userSetings.value = resp; } })
方案3:使用Nuxt插件初始化store
把store初始化逻辑放到Nuxt插件中,插件会在页面组件挂载前执行:
- 创建
plugins/auth.init.ts:
export default defineNuxtPlugin(() => { const auth = useAuth(); const jsonAuth = useCookie('auth'); if (jsonAuth.value) { auth.setLoginPayload(jsonAuth.value); } })
这样页面组件的onMounted执行时,store已经完成初始化,无需额外等待。
内容的提问来源于stack exchange,提问作者Adrian
相关产品推荐
相关产品推荐

