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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 01:50:25