前端UI组件【免费下载链接】zagBuild your design system in React, Solid, Vue, Svelte or Vanilla. Powered by finite state machines项目地址https://gitcode.com/gh_mirrors/za/zag点击查看免费下载zag-js/svelte是 zag 项目为 Svelte 框架提供的官方适配包它以 Svelte 5 的 runes$state、$derived、$effect为基础把zag-js/core的有限状态机接入 Svelte 组件生命周期。本文以该包的 CHANGELOG0.34.0 → 1.45.0为脉络结合源码与测试系统讲解useMachine、bindable、mergeProps、normalizeProps等核心 API 的设计动机、实现原理与常见修复点帮助你理解状态机 Svelte这套架构如何在多个版本迭代中逐步走向稳定。一、包定位Svelte 与状态机之间的桥梁在 zag 的整体架构中所有组件的交互逻辑都沉淀为框架无关的有限状态机zag-js/core而zag-js/svelte负责把机器翻译成 Svelte 开发者熟悉的响应式 API。从 package.json 可以看到它对外只依赖三个核心包zag-js/core状态机运行时createMachine、状态转换、actions/guards/effectszag-js/types提供createNormalizer等类型与工具zag-js/utils提供callAll、compact、isEqual等基础函数。该包要求svelte 5peerDependencies内部以.svelte.ts后缀的模块组织 runes 代码。src/index.ts 公开的 API 全集为useMachinemachine.svelte.ts核心 HookmergeProps、normalizeProps与PropTypes类型portalportal.tsSvelte action 形式的传送门reflectreflect.ts响应式代理工具useSyncExternalStoreuse-sync-external-store.svelte.ts外部 store 桥接。从版本演进看早期0.34–0.40是 API 定型期0.40.0 将useService与useMachine的 API 与其他框架统一0.46.0 补充了useActorHook 以对齐其他框架0.53.0 修复 Svelte runes 警告、新增reflect工具并公开PropTypes类型0.71.0 起不再随包发布src文件。1.0.0 之后进入稳定的补丁期修复重心转向属性合并、响应式与生命周期细节。二、useMachinerunes 驱动的状态机接入useMachine是整个适配层的核心实现在 machine.svelte.ts。它的输入是机器定义与用户 props输出是一个ServiceT对象包含state、send、context、prop、scope、refs、computed、event、getStatus等成员。2.1 响应式基础$derived 与 $state源码中三处关键的 runes 用法const scope $derived.by(() { const { id, ids, getRootNode } access(userProps) as any return createScope({ id, ids, getRootNode }) }) const props: any $derived(machine.props?.({ props: compact(access(userProps)), scope }) ?? access(userProps)) let propsAtStop: any const prop useProp(() (status MachineStatus.Stopped ? propsAtStop : props))scope与props都是$derived当用户传入的 props支持函数形式变化时机器会自动重新解析作用域与属性。这里有一个刻意设计prop读取器在机器已停止时返回propsAtStop快照而不是当前 props。对应 CHANGELOG 1.34.0 修复的state_unsafe_mutation警告——组件拆卸期间发生状态转换会导致状态不安全变更因此拆卸时先用propsAtStop { ...props }冻结现场再执行onDestroy清理保证退出动作读取到的 props/context 仍是停止时的值。测试 machine.test.tsprop and context read after stop return the values at stop专门验证了这一点。2.2 生命周期onMount 启动、onDestroy 收尾onMount(() { const started status MachineStatus.Started status MachineStatus.Started state.invoke(state.initial!, INIT_STATE) }) onDestroy(() { if (status ! MachineStatus.Started) return propsAtStop { ...props } status MachineStatus.Stopped effects.forEach((fn) fn?.()) action(machine.exit) })机器在onMount时启动并进入初始状态onDestroy时按顺序清理全部 state effects 并执行机器级exit动作。0.78.1 版本明确修复了机器在$effect时机而非onMount时机启动/停止的问题并提升了useService在context变化时的响应性——这说明适配层刻意把启动时机锚定在挂载阶段避免与 Svelte 的 effect 调度冲突。1.43.0 又进一步组件在机器启动前被销毁时跳过机器 exit actionsSkip machine exit actions when a component is disposed before the machine starts防止对从未启动的机器执行清理动作。send函数内部先检查status ! MachineStatus.Started直接返回1.6.2 修复确保机器启动后才处理事件随后执行完整的转换流程findTransition查找当前状态对应事件 →choose按 guard 选择第一个通过的转换 →resolveStateValue解析目标 → 若目标变化则state.set(target)若目标相同但设置了reenter: true则重新调用当前状态1.3.0 引入reenter:true支持否则只执行转换 actions。测试中的reenter transition、same-state transition does not reenter by default、reenter transition action order顺序为exit → transition → entry等用例完整覆盖了这些分支。2.3 状态对象的配套能力getState()返回的 state 除了值本身还提供两个常用方法hasTag(tag) { ... } matches(...values) { return values.some((value) matchesState(currentState, value)) }matches支持多值匹配state.matches(idle, loading)hasTag则检查机器标签。在 1.35.0 中核心层全面支持嵌套状态Svelte 适配层同步跟进——nested-states.test.ts 验证了深层嵌套转换dialog.open.viewing、相对目标target: .closed、跨层#id目标、以及嵌套转换时 entry/exit/effect 的执行顺序见 1.35.0 条目ensure effects/entry/exit ordering for nested transitions。三、bindable受控与非受控的统一抽象机器的 context 值通过bindable工厂创建实现在 bindable.svelte.ts。它同时支持受控外部传入value与非受控内部$state自持两种模式export function bindableT(props: () BindableParamsT): BindableT { const initial props().value ?? props().defaultValue const eq props().isEqual ?? Object.is let value $state(initial) const controlled () props().value ! undefined ... }3.1 取值优先级value 先于 defaultValueCHANGELOG 1.43.0 特别记录了一个行为对齐修复bindable 解析值时value优先于defaultValue与 React/Solid 适配层保持一致。观察initial props().value ?? props().defaultValue与get()中controlled() ? props().value : value的实现可以确认一旦外部传入value读取一律走外部值外部不传时才回落到内部$state。这种受控优先策略保证了跨框架行为一致。3.2 同步与更新策略set方法支持传入函数式更新(prev) next内部用valueRef追踪最新值通过props().sync ? flushSync : identity决定是否同步冲刷flushSync包裹queueMicrotask见machine.svelte.ts的flush变更时若isEqual判定值不同才触发onChange1.8.0 为 bindable 函数补充了ref与cleanup能力bindable.ref提供轻量引用容器bindable.cleanup注册销毁回调便于组合使用中状态create using state compositions。useMachine内部把 context 包装成BindableContextTget/set/initial/hash机器的 actions、guards、computed 均通过context.get(key)读取。受控场景的测试context: controlled验证了外部传value: foo时即使内部执行VALUE.SET也不会覆盖外部值。四、mergeProps属性合并的持续打磨mergeProps是设计系统维护者使用最频繁的工具负责把机器 API 返回的 props 与用户 props 合并。Svelte 版在 merge-props.ts 中基于 zag-js/core 的 mergeProps 之上做 Svelte 定制其演进贯穿多个版本4.1 class从字符串拼接到 ClassValue 数组1.26.4 版本将mergeProps的 class 返回值重构为数组把解析职责交给 Svelte 5.15 的原生 class 处理内部使用clsx。源码中const classNames: any[] [] for (const props of args) { if (class in props props.class ! null) classNames.push(props.class) } const merged zagMergeProps(...args) if (classNames.length 0) { merged.class classNames.length 1 ? classNames[0] : classNames }这意味着你可以传入字符串、数组、对象条件类名混用的 class 值而不必担心拼接错误。merge-props.test.ts 用clsx(props.class)模拟 Svelte 运行时解析覆盖了数组、嵌套数组、对象、falsy 过滤等场景。4.2 style字符串解析与分号保留style 合并经历了两次重要修复0.78.0修复 SveltemergeProps中 style 合并1.45.0修复mergeProps截断含分号内联 style 值的问题包括带引号的 CSS 自定义属性如--label:a;b和data URL如url(data:image/svgxml;base64,...)。实现上mergeProps先用serialize()把 style 字符串解析为对象再通过toStyleString重新序列化。serialize是一个带状态的小型解析器它追踪引号/、转义符、括号深度(/)与注释/* */只在括号深度为 0 且不在引号内时才把;当作声明分隔符。这样url(...)内部的;和引号内的;都不会被误切。对应测试keeps semicolons inside quoted values and url() when combining styles精确验证了这一行为。4.3 事件处理器与>export function reflectT extends Recordstring, any(obj: () T): T { return new Proxy(obj() as T, { get(_, prop) { const target obj() let value Reflect.get(target, prop) return isFunction(value) ? value.bind(target) : value }, }) }每次属性访问都重新求值obj()可传入$derived表达式从而拿到最新的 API 对象函数属性自动bind(target)保证this正确。它解决的核心痛点是把机器 connect 出的api传给组件时如果只捕获了旧对象引用响应式更新会丢失通过reflect每次访问都取最新值实现懒响应。6.2 portalSvelte action 形式的传送门portal.ts 导出portal(node, props)action支持disabled、container、getRootNode三个配置默认挂载到document.body可指定容器或自定义根节点Shadow DOM 场景disabled时保持原位。它返回标准的{ destroy, update }随 Svelte action 生命周期自动更新与销毁。6.3 track 与 useSyncExternalStoretracktrack.svelte.ts用于machine.watch通过$effect对比依赖数组前后值isEqual深度比较只在真正变化时执行回调——对应 1.12.3提升事件未触发状态转换时的响应性与 1.12.4提升同步状态更新的响应性两条修复useSyncExternalStore以$state保存快照$effect订阅外部 store支持服务端快照参数getServerSnapshot。SSR 场景下typeof window undefined直接取服务端快照见 ssr.test.ts服务端渲染不执行根级 exit actions。七、响应性、时序与警告类修复的演进脉络CHANGELOG 中相当一部分条目属于Svelte 5 运行时磨合理解它们有助于避开同样的坑版本修复内容说明0.78.1机器启动/停止从$effect移到onMount时机锚定生命周期避免 effect 调度竞态1.13.0修复 Svelte5.28.3的微任务时序问题适配新版 runes 调度1.21.3移除 refs/events 中的状态性对象运行时性能优化1.24.0修复状态引用捕获初始值而非当前响应式值的警告避免闭包捕获旧 state1.34.0修复组件拆卸期间状态转换导致的state_unsafe_mutation警告拆卸时冻结 props 快照1.41.0修复 React 19 Strict Mode 下 dialog/drawer/popover 关闭后body残留data-scroll-lock、data-inert、overflow:hidden状态机清理幂等性1.43.0组件在机器启动前被销毁时跳过 exit actions防止清理未启动的机器1.45.0修复延迟清理在组件卸载后产生的derived_inert警告bindable 避免组件拥有的 derived这些条目共同指向一个设计原则状态机的副作用必须与 Svelte 组件生命周期严格对齐——启动要在挂载后、清理要在卸载时、且清理过程本身不能触碰已失效的响应式状态。machine.svelte.ts中state的 onChange 回调在退出状态时同步执行 effects 清理、effects用 Map 按状态路径管理清理函数正是这条原则的具体实现。八、从源码到实践一个完整的 Popover 示例仓库的 examples/svelte-ts 提供了 128 个 Svelte 示例页面全部基于zag-js/svelte。以 popover.svelte 为例可以看清整条使用链路script langts import * as popover from zag-js/popover import { normalizeProps, useMachine } from zag-js/svelte const id $props.id() const service useMachine(popover.machine, { id }) const api $derived(popover.connect(service, normalizeProps)) /script button {...api.getTriggerProps()} Click me /button div {...api.getPositionerProps()} div {...api.getContentProps()} ... /div /div关键点useMachine创建 servicepopover.connect(service, normalizeProps)生成组件 API因为api由$derived驱动机器状态变化会触发重渲染getTriggerProps()等返回的 props 内部已经过mergeProps处理展开后即可直接使用。配合 use-controls.svelte.ts 的$state控件上下文可以构建交互式 playground。九、如何阅读与验证这些行为该包自带完整的 vitest 测试套件pnpm test见 package.json建议按以下顺序阅读machine.test.ts状态机核心行为——初始状态、entry/exit、转换 actions、guard 回退、reenter、事件 current/previous 追踪、拆卸后忽略事件、同 tick 内连续 send 的确定性处理nested-states.test.ts嵌套状态与相对/#id目标解析merge-props.test.tsclass/style/事件/data-ownedby/symbol 键的合并语义ssr.test.ts服务端渲染下的生命周期行为。测试基础设施 render.ts 与 MachineHarness.svelte 展示了如何用svelte的mount/settled/unmount在测试中驱动机器这套模式同样适用于你自己的组件测试。结语回看 0.34 → 1.45 的整个 CHANGELOGzag-js/svelte的演进主线十分清晰早期定型 API 形态useMachine/useService/useActor、reflect、PropTypes中期适配 Svelte 5 runes 并修正生命周期与响应式细节后期则集中打磨mergeProps的属性合并精度class 数组化、style 分号保留、data-ownedby组合。对于在 Svelte 中构建设计系统的团队这套适配层提供了一个可复用的范式逻辑收敛于框架无关的有限状态机框架层只负责响应式桥接与属性归一化从而让同一套机器代码在 React、Solid、Vue、Svelte 与 Vanilla 之间保持一致行为。赞分享前端UI组件【免费下载链接】zagBuild your design system in React, Solid, Vue, Svelte or Vanilla. Powered by finite state machines项目地址https://gitcode.com/gh_mirrors/za/zag点击查看免费下载相关推荐Zag 1.x 版本演进全解析从状态机架构到 1.45 关键能力Zag 1.x 版本演进全解析从状态机架构到 1.45 关键能力 本文以 Zag 开源仓库根目录 CHANGELOG.md https://link.gitc前端UI组件zag-js/solid 演进全解析从有限状态机到 Solid 响应式适配器的技术变迁zag js/solid 演进全解析从有限状态机到 Solid 响应式适配器的技术变迁 本文以 Zag 仓库中 packages/frameworks/so前端UI组件cypress/svelte 组件适配器版本演进与源码解析从 Svelte 4 挂载到 Svelte 5 生态迁移cypress/svelte 组件适配器版本演进与源码解析从 Svelte 4 挂载到 Svelte 5 生态迁移 cypress/svelte 是 Cy测试质量保障前端接口测试上一篇如何彻底清理显卡驱动Display Driver Uninstaller终极指南下一篇永久保存QQ空间青春记忆GetQzonehistory一键备份完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考