You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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执行。用户刷新页面时的流程是:

  1. 页面组件的onMounted触发,调用$authFetch
  2. $authFetch读取Pinia store,此时store还是初始的null状态
  3. 之后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初始化完成后再执行组件逻辑

  1. 在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;
    }
  }
})
  1. 在app.vue的onMounted中调用初始化方法:
onMounted(async () => {
  const auth = useAuth();
  await auth.initFromCookie();
})
  1. 在组件中等待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插件中,插件会在页面组件挂载前执行:

  1. 创建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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 07:13:16