在@xstate/svelte中等待上下文动态生成的AuthService问题
问题:Svelte上下文动态注入AuthService后的状态等待逻辑实现
我基于Xstate实现了认证存储(Authentication store),会根据Config Machine从后端获取的配置选择actors创建AuthMachine,并将其服务存入Svelte上下文。现在需要在主页面实现以下逻辑:
- 未获取到authService时显示加载
- 30秒未获取到则显示错误
- 获取到后等待其进入idle状态,再根据认证状态显示对应页面
尝试两种方案均失败:
- 在onMount中调用getContext触发Svelte生命周期错误
- 轮询检测authService始终返回false,最终触发错误fallback
日志显示AuthMachine已创建,但页面无法检测到上下文里的authService。页面结构为 <GlobalConfigProvider><AuthProvider><Main></Main></AuthProvider></GlobalConfigProvider>,求正确实现方式。
相关代码片段
export function createAuthMachine(fakeLogin) { const chosenActors = fakeLogin === "true" || fakeLogin === true ? fakeActors : realActors; return setup({ actions, actors: chosenActors }).createMachine(
<!-- GlobalConfigProvider --> <script> import { useMachine } from '@xstate/svelte'; import { setContext } from 'svelte'; import {globalConfigMachine} from "../../lib/config/index.mjs"; const { snapshot, send } = useMachine(globalConfigMachine); $: if ($snapshot.matches('done')) { setContext('globalConfig', $snapshot.context.config); } setContext('globalConfigSnapshot', snapshot); export let config = $snapshot.context.config; </script> <slot {config}></slot>
<!-- AuthProvider --> <script> import {getContext, setContext} from "svelte"; import {useMachine} from "@xstate/svelte"; import {createAuthMachine} from "$lib/auth/index.js"; const globalConfigSnapshot = getContext('globalConfigSnapshot'); ... $: if ($globalConfigSnapshot?.matches?.('done')) { const config = $globalConfigSnapshot.context.config; const authMachine = createAuthMachine(config.fakeLogin); authService = useMachine(authMachine, { input: config }); setContext('authService', authService); } </script> <slot/>
解决方案
核心问题分析
Svelte的getContext是同步获取逻辑,而你的AuthService是异步注入的(依赖GlobalConfigMachine完成配置加载),直接同步获取或简单轮询无法响应上下文的动态更新。
正确实现步骤
1. 优化AuthProvider的上下文注入逻辑
用可写变量作为中间层,实现上下文的动态更新通知,避免直接在反应式语句中调用setContext:
<!-- AuthProvider --> <script> import { getContext, setContext, writable } from "svelte"; import { useMachine } from "@xstate/svelte"; import { createAuthMachine } from "$lib/auth/index.js"; const globalConfigSnapshot = getContext('globalConfigSnapshot'); // 创建可写变量存储authService,用于后代组件订阅 const authServiceStore = writable(null); setContext('authServiceStore', authServiceStore); $: if ($globalConfigSnapshot?.matches?.('done')) { const config = $globalConfigSnapshot.context.config; const authMachine = createAuthMachine(config.fakeLogin); const authService = useMachine(authMachine, { input: config }); // 更新可写变量,触发后代组件的响应式更新 authServiceStore.set(authService); } </script> <slot/>
2. 主页面(Main组件)实现完整状态逻辑
通过订阅可写变量捕获AuthService,结合定时器实现超时,同时用Xstate的waitFor等待指定状态:
<!-- Main组件 --> <script> import { getContext, onMount } from "svelte"; const authServiceStore = getContext('authServiceStore'); let authService = null; let isLoading = true; let hasError = false; let timeoutId; // 启动30秒超时定时器 function startTimeout() { timeoutId = setTimeout(() => { isLoading = false; hasError = true; }, 30000); } // 订阅authService的变化 authServiceStore.subscribe(async (service) => { if (service) { authService = service; // 取消超时定时器 clearTimeout(timeoutId); isLoading = true; // 等待AuthMachine进入idle状态 await service.waitFor(state => state.matches('idle')); isLoading = false; } }); // 组件挂载时启动超时 onMount(() => { startTimeout(); // 组件销毁时清理定时器 return () => clearTimeout(timeoutId); }); </script> {#if hasError} <div>加载超时,请刷新页面重试</div> {:else if isLoading} <div>加载中...</div> {:else} {#if $authService.snapshot.matches('authenticated')} <div>已认证页面内容</div> {:else} <div>未认证页面内容</div> {/if} {/if}
3. GlobalConfigProvider的小优化(可选)
将配置也存入可写变量,方便后代组件更灵活地订阅:
<!-- GlobalConfigProvider --> <script> import { useMachine } from '@xstate/svelte'; import { setContext, writable } from 'svelte'; import { globalConfigMachine } from "../../lib/config/index.mjs"; const { snapshot, send } = useMachine(globalConfigMachine); setContext('globalConfigSnapshot', snapshot); // 用可写变量存储配置 const globalConfigStore = writable(null); $: if ($snapshot.matches('done')) { globalConfigStore.set($snapshot.context.config); } setContext('globalConfigStore', globalConfigStore); </script> <slot config={$globalConfigStore}/>
关键原理说明
- Svelte上下文本身是静态的,无法直接更新,通过可写变量作为中间层,可以实现动态更新的通知
- 订阅可写变量能准确捕获AuthService的创建时机,避免轮询的低效问题
- Xstate的
service.waitFor方法可以优雅等待机器进入指定状态,无需手动轮询状态变化
内容的提问来源于stack exchange,提问作者Jackie
相关产品推荐
相关产品推荐

