基于Svelte的类FSM导航栈式上下文追踪实现问询
基于Svelte的栈式FSM导航上下文系统实现问题
背景与需求
我正在实现一个栈式上下文追踪系统,用于类FSM(有限状态机)的组件导航方案,适配路由能力较弱的环境(比如移动游戏)。
编辑说明:我已将3个FSM合并为一个,无需再使用嵌套FSM!
使用场景
我有outer和inner两个组件文件夹,用来归类类FSM导航组件视图。每个文件夹下存储各类组件,并有如下索引目录(TOC):
// src/lib/outer/index.js import { default as A } from './A.svelte' import { default as B } from './B.svelte' import { default as C } from './C.svelte' const lookups = new Map () // 组件别名映射 lookups.set ('A', A) lookups.set ('B', B) lookups.set ('C', C) // 通过别名获取组件 export const view = (name) => lookups.get (name)
通过这种方式可以轻松获取并导入组件,示例如下:
// src/lib/fsm-system/FSMViews.svelte <svelte:component this={ ctx.$view } { ctx } /> <script lang="ts"> export let ctx = null </script>
用户调用代码示例:
// src/routes/merged.svelte <h1> Mergeable FSM test </h1> <FSMViews { ctx } /> <script lang="ts"> import { FSMContext, FSMViews } from '$lib/fsm-system' import { view } from '$lib/outer' // 测试用SvelteKit const ctx = FSMContext ({ debug: true }) const data = { x: 42 } ctx.push ({ view, data }) ctx.show('A') </script>
核心问题
关键在于正确编写FSMContext,它需要满足:
- 是一个栈数组,每一层存储一个FSM上下文(当前视图+负载数据)
- 具备响应式(可写或派生存储)
- 可使用JS Proxies
但目前我无法正确追踪栈的末尾项,该条目应包含:
- 当前视图名称
- 当前层级的TOC函数(来自outer/index.js)
- 该层级的存储负载
实现方案建议
1. 基于Svelte可写存储构建栈结构
利用Svelte的writable存储天生的响应式特性,结合数组实现栈结构,同时封装push/pop/show等方法,确保每次操作都触发响应式更新。
// src/lib/fsm-system/FSMContext.ts import { writable, get, type Writable } from 'svelte/store' // 定义单一层级的上下文类型 interface FSMLevel { view: (name: string) => any // TOC组件查找函数 data: Record<string, any> // 负载数据 currentViewName: string | null // 当前激活的视图名称 } // FSMContext实例的对外接口 interface FSMContextInstance { stack: Writable<FSMLevel[]> push: (level: Omit<FSMLevel, 'currentViewName'>) => void pop: () => FSMLevel | undefined show: (viewName: string) => void // 派生的当前活跃上下文(栈顶) current: Writable<FSMLevel | null> } export function FSMContext(options?: { debug?: boolean }): FSMContextInstance { // 初始化栈存储 const stack = writable<FSMLevel[]>([]) // 派生当前栈顶的响应式存储 const current = writable<FSMLevel | null>(null) // 监听栈变化,自动更新current stack.subscribe((levels) => { current.set(levels.at(-1) || null) options?.debug && console.log('FSM Stack updated:', levels) }) // 实现push方法:添加新层级到栈顶,默认无激活视图 function push(level: Omit<FSMLevel, 'currentViewName'>) { stack.update((prev) => [...prev, { ...level, currentViewName: null }]) } // 实现pop方法:移除栈顶层级 function pop() { let popped: FSMLevel | undefined stack.update((prev) => { popped = prev.pop() return prev }) return popped } // 实现show方法:更新当前栈顶的激活视图名称 function show(viewName: string) { stack.update((prev) => { if (prev.length === 0) return prev const lastIndex = prev.length - 1 return [ ...prev.slice(0, lastIndex), { ...prev[lastIndex], currentViewName: viewName } ] }) } return { stack, push, pop, show, current } }
2. 优化FSMViews组件的响应式消费
修改FSMViews组件,直接订阅current存储,自动响应栈顶上下文的变化:
// src/lib/fsm-system/FSMViews.svelte <script lang="ts"> import type { FSMContextInstance } from './FSMContext' export let ctx: FSMContextInstance // 自动订阅当前上下文变化 $: currentCtx = $ctx.current </script> {#if currentCtx && currentCtx.currentViewName} <svelte:component this={currentCtx.view(currentCtx.currentViewName)} {ctx} data={currentCtx.data} /> {/if}
3. 使用Proxy简化API调用(可选)
如果希望简化API调用(比如直接通过ctx.view获取当前视图),可以用Proxy包装FSMContext实例,自动代理到当前栈顶的属性:
// 在FSMContext函数末尾添加Proxy包装 return new Proxy({ stack, push, pop, show, current } as FSMContextInstance, { get(target, prop) { // 自动代理栈顶属性的访问 if (['view', 'data', 'currentViewName'].includes(prop as string)) { const currentVal = get(target.current) return currentVal ? currentVal[prop as keyof FSMLevel] : undefined } // 其他属性直接返回原实例的方法/存储 return (target as any)[prop] } }) as FSMContextInstance
这样用户代码中可以直接写ctx.view(ctx.currentViewName),无需手动处理订阅逻辑。
关键注意点
- 响应式触发:所有对栈的修改必须通过
stack.update()方法,确保Svelte能捕获到变化并更新视图。 - 栈顶自动同步:通过
subscribe监听栈的变化,自动维护current存储,避免手动同步栈顶状态。 - 类型安全:用TypeScript定义清晰的类型接口,减少运行时错误。
内容的提问来源于stack exchange,提问作者Hefeust CORTES
相关产品推荐
相关产品推荐

