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

Svelte+urql优先从Store取登录用户,首次渲染返回undefined问题

问题:Svelte + urql 首次渲染用户值为undefined,后续更新正常

我正尝试在Svelte与urql中实现一个功能:仅当Store中不存在已登录用户时,才从API获取该用户。

登录提交表单中存入Store的代码:

function onSubmit() {
  if ($data?.user) {
    userLogged.update((s) => ({
      ...s,
      user: $data?.user,
    }));
  }
});

在另一个组件中尝试优先从Store取值,不存在则从API获取的代码:

<script lang="ts">
let user: any;
const getUser = () => {
    let user = get(userLogged);

    if (user.user !== null) {
      return user;
    } else {
      const userQuery = queryStore({
        client: getContextClient(),
        query: USER_QUERY,
      });
      userQuery.subscribe((val) => {
        console.log("val ?", val?.data?.user);
        userLogged.update((s) => ({ ...s, user: val?.data?.user }));

        user = val?.data?.user;
       
      });
      return user;
    }
  };
user = getUser();
console.log("user", user);
</script>

页面刷新后首次渲染时,console.log("user", user)返回undefined,后续组件更新时才会正确返回用户。但subscribe回调内的console.log始终能输出正确值,且已将其赋值给user变量,无法理解原因。


原因分析

问题核心是异步操作的时序不匹配:

  • queryStore的subscribe是异步触发的,API请求需要时间才能返回数据。当你调用getUser()并返回user时,subscribe的回调还没执行,此时user还是初始的undefined,所以首次console.log输出undefined。
  • 后续API请求完成后,subscribe回调执行,给user赋值的同时更新了userLogged store。store的变化会触发组件重新渲染,此时getUser()会从store中取到已存在的用户值,因此能正确返回。

解决方案

方案1:利用Svelte响应式特性简化逻辑

直接通过$userLogged订阅store,结合urql的queryStore自动状态管理,无需手动写subscribe:

<script lang="ts">
import { queryStore } from '@urql/svelte';
import { getContextClient } from '$lib/utils';
import USER_QUERY from './queries/user.query';

// 自动订阅userLogged store
const { user: storedUser } = $userLogged;

// 仅当store无用户时,创建查询实例
const userQuery = storedUser === null ? queryStore({
  client: getContextClient(),
  query: USER_QUERY,
}) : null;

// 响应式推导用户值:优先取store数据,无则取查询结果
$: user = storedUser ?? $userQuery?.data?.user;

// 查询返回数据后,同步更新store
$: if ($userQuery?.data?.user) {
  userLogged.update(s => ({ ...s, user: $userQuery.data.user }));
}
</script>

{#if user}
  <div>欢迎,{user.name}</div>
{:else}
  <div>加载中...</div>
{/if}

方案2:修正原函数的异步逻辑

如果坚持使用原函数写法,需通过Promise处理异步返回:

<script lang="ts">
let user: any;
const getUser = async () => {
    const storedUser = get(userLogged);
    if (storedUser.user !== null) {
      return storedUser.user;
    } else {
      return new Promise(resolve => {
        const userQuery = queryStore({
          client: getContextClient(),
          query: USER_QUERY,
        });
        const unsubscribe = userQuery.subscribe((val) => {
          if (val?.data?.user) {
            userLogged.update(s => ({ ...s, user: val.data.user }));
            resolve(val.data.user);
            unsubscribe(); // 拿到数据后取消订阅
          }
        });
      });
    }
  };

// 异步获取用户并赋值
getUser().then(fetchedUser => {
  user = fetchedUser;
  console.log("user", user);
});
</script>

关键注意点

  • Svelte中$:标记的语句会响应依赖变化自动重新执行,适合处理响应式逻辑。
  • urql的queryStore本身是store,使用$userQuery可自动订阅状态变化,无需手动调用subscribe。
  • 同步函数无法直接返回异步操作的结果,必须通过Promise等异步方式处理。

内容的提问来源于stack exchange,提问作者Dujard

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 19:40:30