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赋值的同时更新了userLoggedstore。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
相关产品推荐
相关产品推荐

