@ariakit/solid-store 完全指南:Ariakit 的 Solid 状态原语 API 与实现原理
发布时间:2026/9/26 2:15:14来源:尧图网络
UI组件前端【免费下载链接】ariakitToolkit with accessible components, styles, and examples for your next web app项目地址https://gitcode.com/gh_mirrors/ar/ariakit点击查看免费下载导读ariakit/solid-store是 Ariakit 在 Solid 生态侧的状态原语入口包负责把框架无关的 Store 工具以 ESM 纯模块的形式暴露给 Solid 开发者。本文以该包的官方 readmepackages/ariakit-solid-store/readme.md为主体骨架完整覆盖createStore、setup、init、subscribe、sync、batch、pick、omit、mergeStore、throwOnConflictingProps等全部 14 个导出项的类型签名与用法并深入ariakit/store的真实源码packages/ariakit-store/src/index.ts与测试用例packages/ariakit-store/src/test.ts讲清楚订阅/同步/批量三种监听时机的差异、Store 与父 Store 之间的双向同步机制以及不可变快照、键级过滤等底层设计。读完本文你将能够独立使用这套 Store 原语构建可组合、可派生的状态层并理解 Ariakit 组件状态管理的底层契约。一、包定位Solid 侧的状态原语入口从 packages/ariakit-solid-store/package.json 可以看到该包名为ariakit/solid-store版本0.1.10描述为 Ariakit Solid store utilities关键字为ariakit、solid、store。readme 开篇给出了两个重要声明这是 Ariakit 的内部依赖包不遵循语义化版本semver——patch 或 minor 版本中可能发生破坏性变更。这意味着如果你直接依赖它升级时需要保持谨慎它是 Ariakit Store 原语的 Solid 侧入口——目前只是把框架无关的 Store 工具从ariakit/store再导出re-export。这两个声明在源码中得到了一一印证。packages/ariakit-solid-store/src/index.ts 的全部内容只有一行export * from ariakit/store;而 packages/ariakit-store/src/index.ts 才是真正的实现所在createStore、setup、init、subscribe、sync、batch、omit、pick、mergeStore、throwOnConflictingProps以及State、StoreOptions、StoreProps、StoreState、Store等类型全部定义于此。readme 中re-exports the framework-agnostic store helpers fromariakit/store的描述与源码完全一致。在 monorepo 层面pnpm-workspace.yaml 将ariakit/solid-store与ariakit/store都登记为 workspace 包package.json中依赖声明为ariakit/store: workspace:*构建脚本为ariakit build --index-only只构建索引入口。另外值得说明的是包的分发形态ESM-onlypackage.json中type: module单一公开入口exports字段只暴露.指向./src/index.ts和./package.json无副作用sideEffects: false便于打包器做 tree-shaking。从仓库现状看packages/ariakit-solid-components/src 仅包含as、role、heading、separator、visually-hidden、focus-trap、group等基础组件当前 Solid 组件包内还没有消费 Store 原语的组件因此该包现阶段以独立、可复用的工具库形态存在供 Solid 应用直接引用。二、安装与快速上手readme 给出的安装命令npm i ariakit/solid-store在 pnpm monorepo 环境中也可以通过 workspace 协议引用如 pnpm-workspace.yaml 所示。注意由于包不遵循语义化版本锁定精确版本是更稳妥的做法仓库的pnpm-workspace.yaml中亦配置了saveExact: true。从包根导入工具函数import { createStore } from ariakit/solid-store;一个最小可运行示例——创建 Store、订阅变化、更新状态import { createStore, subscribe, sync, batch } from ariakit/solid-store; // 创建 Store传入初始状态 const store createStore({ count: 0, label: a }); // 订阅状态变化后异步时机之外、事件触发后回调 const unsubscribe subscribe(store, [count], (state, prevState) { console.log(state.count, prevState.count); // 1 0 }); store.setState(count, 1); // 触发订阅 unsubscribe(); // 取消订阅 // setState 支持函数式更新updater store.setState(count, (count) count 1);setState是同步生效的store.getState()立即可见新值。关于这一点测试 packages/ariakit-store/src/test.ts 中的 sets state with updater functions 用例给出了精确断言连续两次store.setState(count, (count) count 1)后getState()返回{ count: 2 }监听器恰好被调用两次最后一次收到的参数是({ count: 2 }, { count: 1 })。三、API 参考完整签名以下为 readme 中完整列出的 API全部导出自包根。各签名以原文档为准并结合源码补充语义说明。createStorefunction createStoreS extends State( initialState: S, ...stores: ArrayStorePartialS | undefined ): StoreS;创建 Store。第一个参数为初始状态后续可选参数是父 Store列表——新 Store 会与这些父 Store 保持同步详见下文父 Store 双向同步一节。对应实现位于 packages/ariakit-store/src/index.ts。setuptype StoreSetup (callback: () void | (() void)) () void; function setupT extends Store( store?: T | null, ...args: ParametersStoreSetup ): T extends Store ? ReturnTypeStoreSetup : void;注册一个回调该回调会在 Store初始化init时被调用。回调可以返回一个清理函数供 Store 销毁时执行。返回的也是取消注册函数。inittype StoreInit () () void; function initT extends Store( store?: T | null, ...args: ParametersStoreInit ): T extends Store ? ReturnTypeStoreInit : void;在 Store 初始化时应当被调用的函数。init返回一个清理函数。从实现看packages/ariakit-store/src/index.tsinit保证 Store只初始化一次——即使同一个 Store 被传入多个其他 Store 中但 Store 必须等所有实例都卸载后才能销毁。源码注释引用了 Ariakit 的 issue #3147 说明这一设计动机。subscribetype ListenerS (state: S, prevState: S) void | (() void); type SyncS, K extends keyof S ( keys: K[] | null, listener: ListenerPickS, K, ) () void; type StoreSubscribeS State, K extends keyof S keyof S SyncS, K; function subscribeT extends Store, K extends keyof StoreStateT( store?: T | null, ...args: ParametersStoreSubscribeStoreStateT, K ): T extends Store ? ReturnTypeStoreSubscribeStoreStateT, K : void;注册一个监听函数在 Store 状态变化之后被调用。注意两点keys用于筛选只有这些键发生变化时才触发回调传null表示监听全部键回调收到(state, prevState)其中state被PickS, K收窄为所选键的类型。synctype ListenerS (state: S, prevState: S) void | (() void); type SyncS, K extends keyof S ( keys: K[] | null, listener: ListenerPickS, K, ) () void; type StoreSyncS State, K extends keyof S keyof S SyncS, K; function syncT extends Store, K extends keyof StoreStateT( store?: T | null, ...args: ParametersStoreSyncStoreStateT, K ): T extends Store ? ReturnTypeStoreSyncStoreStateT, K : void;注册一个监听函数注册时立即调用一次之后每当 Store 状态变化都同步调用。sync是 Ariakit 内部组件保持派生状态一致性的关键工具例如open与mounted两个键的联动。batchtype ListenerS (state: S, prevState: S) void | (() void); type SyncS, K extends keyof S ( keys: K[] | null, listener: ListenerPickS, K, ) () void; type StoreBatchS State, K extends keyof S keyof S SyncS, K; function batchT extends Store, K extends keyof StoreStateT( store?: T | null, ...args: ParametersStoreBatchStoreStateT, K ): T extends Store ? ReturnTypeStoreBatchStoreStateT, K : void;注册一个监听函数注册时立即调用一次之后在一批状态变化之后微任务时机被调用。batch适合做批量提交后再刷新的场景例如表单校验、动画状态归并。三者时机对比subscribe在每次变更后触发sync在注册时立即触发、随后每次变更同步触发batch在注册时立即触发、随后把同一微任务内多次变更合并为一次触发。源码注释明确说明这三个类型签名故意相同都是SyncS, K差异只在运行时时机语义而不是类型层面见 packages/ariakit-store/src/index.ts。omittype StoreOmit S State, K extends ReadonlyArraykeyof S ReadonlyArraykeyof S, (keys: K) StoreOmitS, K[number]; function omitT extends Store, K extends ReadonlyArraykeyof StoreStateT( store?: T | null, ...args: ParametersStoreOmitStoreStateT, K ): T extends Store ? ReturnTypeStoreOmitStoreStateT, K : void;创建一个新 Store其状态为当前 Store 状态剔除指定键后的子集并与原 Store 保持同步。picktype StorePick S State, K extends ReadonlyArraykeyof S ReadonlyArraykeyof S, (keys: K) StorePickS, K[number]; function pickT extends Store, K extends ReadonlyArraykeyof StoreStateT( store?: T | null, ...args: ParametersStorePickStoreStateT, K ): T extends Store ? ReturnTypeStorePickStoreStateT, K : void;创建一个新 Store其状态为当前 Store 状态只保留指定键后的子集并与原 Store 保持同步。pick与omit在 readme 中的描述相同Creates a new store with a subset of the current store state and keeps them in sync区别仅在于保留还是剔除。从实现看packages/ariakit-store/src/index.ts两者都通过createStore(_pick(state, keys), finalStore)/createStore(_omit(state, keys), finalStore)实现——即把当前状态裁剪后作为新 Store 的初始状态并把原 Store 作为父 Store 传入从而获得双向同步。mergeStorefunction mergeStoreS extends State( ...stores: ArrayStoreS | undefined ): StoreS;将多个 Store 合并为一个 Store。实现packages/ariakit-store/src/index.ts分三步遍历所有 Store用Object.assign把各自getState()的结果合并成初始状态以该初始状态调用createStore(initialState, ...stores)把所有 Store 作为父 Store 接入同步返回Object.assign({}, ...stores, store)——把各 Store 实例的方法合并到新 Store 上消费方拿到的是一个组合后的完整对象。throwOnConflictingPropsfunction throwOnConflictingProps(props: AnyObject, store?: Store): void;当传入 Store prop 的同时又传入了默认状态default state时抛出错误。实现位于 packages/ariakit-store/src/index.ts仅在开发环境process.env.NODE_ENV ! production生效扫描 props 中以default开头的键把defaultValue之类的键名还原成状态键value若该状态键已存在于传入 Store 的状态中则抛错并给出纠错指引——默认状态应当传给最顶层的 Store 创建函数而不是与storeprop 同时混用。Statetype State AnyObject;Store 状态类型。任何对象都可以作为状态。StoreOptionstype StoreOptionsS extends State, K extends keyof S PartialPickS, K;可以传给 Store 创建函数的初始状态类型。测试用例packages/ariakit-store/src/test.ts验证了StoreOptionsSampleState, count | label精确等价于PartialPickSampleState, count | label。StorePropsinterface StorePropsS extends State State { /** * Another store object that will be kept in sync with the original store. * * Live examples: * - [Navigation Menubar](https://ariakit.com/examples/menubar-navigation) */ store?: StorePartialS; }可以传给 Store 创建函数的 props。唯一的字段store表示另一个 Store 对象将与原 Store 保持同步。仓库中对应的真实示例可参考 examples/menubar-navigation/index.react.tsx导航型菜单栏通过共享 Store 在多个菜单之间同步展开状态。StoreStatetype StoreStateT T extends Storeinfer S ? S : never;从 Store 类型中提取状态类型。这是subscribe、sync、batch、pick、omit等函数泛型签名的基础——它们都通过StoreStateT拿到状态类型后再做Pick/Omit。Storeinterface StoreS State { /** * Returns the current store state. */ getState(): S; /** * Sets a state value. */ setStateK extends keyof S(key: K, value: SetStateActionS[K]): void; }Store 的最小接口getState()返回当前状态setState(key, value)设置某个键的值value既可以是直接值也可以是函数式更新SetStateActionS[K]即(prev) next。任何满足该接口的对象都可以作为StoreT传给上述各函数这也是 Store 层与框架解耦的根本原因——Solid 与 React 侧都复用同一套ariakit/store原语。四、源码级实现剖析readme 只给出 API 签名这一节结合 packages/ariakit-store/src/index.ts 讲清楚背后的实现机制。4.1 状态与不可变快照createStore内部用闭包持有let state initialState第 386 行。setState更新时构造新对象const nextState { ...state, [key]: nextValue }; state nextState;监听器收到的是不可变快照测试 passes immutable state snapshots to listenerspackages/ariakit-store/src/test.ts断言nextState与initialState不是同一引用且旧快照内容保持不变。setState还有三个关键行为忽略未知键if (!hasOwnProperty(state, key)) return;测试 sets known state keys and ignores unknown keys 验证了对不存在键的写入不会改变状态支持函数式更新通过ariakit/utils的applyState(value, () currentValue)求值严格相等不通知isSameValue判断新旧值相等含 NaN 的特殊处理value ! value other ! other时直接返回不触发监听器。测试 does not notify listeners for strict-equal values 验证了这一点CHANGELOG 中 0.1.5 也记载了 Fixed store subscriptions to respond consistently to updates made withNaNkeys。4.2 键级监听与快速路径subscribe/sync/batch的第一个参数keys决定监听粒度null监听所有键存入allKeysListeners键数组只监听这些键的变化存入listenersByKey映射每个键对应一个SetListener。源码在runListeners第 655-705 行中实现了键级快速路径keyed fast path当没有全键监听器时只从listenersByKey.get(updatedKey)取出该键对应的监听器集合逐个通知跳过全量遍历只有存在全键监听器时才退化为 live 遍历。FastPathFrame等数据结构用于在派发过程中动态注册/注销监听器时保证当前派发键的监听器仍能按插入顺序正确收到通知对应测试 fires a keyed listener added during the keyed fast path 等用例。另外keys数组在注册时会被快照拷贝[...keys]后续对原数组的修改不影响注册结果测试 captures selected state keys when subscribing 验证了这一点。4.3 父 Store 双向同步与防环createStore(initialState, ...stores)的stores是父 Store。init初始化时第 428-482 行对每个父 Store找出子状态与父状态共有的键通过sync(store, [key], ...)为每个共有键建立父→子同步父值变化时把新值推给子 Store并带有fromStores内部标记防止回环把父 Store 的当前值逐个、实时地推入子状态注释说明由于子监听器可能在推送早期键时回写父 Store必须实时读取而非使用陈旧快照。反向同步发生在setState中第 711-783 行本地发起的变更会**扇出fan-out**到所有父 Storeif (!fromStores stores.length)。两个短路条件都是关键!fromStores阻止父→子更新再次扇回父 Store 造成死循环stores.length让最常见的无父 Store路径直接跳过扇出。若父 Store 的监听器在扇出过程中改写了同一个键导致值被覆盖superseded源码会用最多MAX_REPAIR_PASSES 100轮的修复循环把最新值重新推给所有父 Store直到收敛开发环境下若 100 轮仍未收敛会打印console.warn提示父监听器可能在循环改写该键。4.4 batch 的微任务合并batch的实现第 633-641、792-818 行依赖一个batchPending标志与queueMicrotask每次setState把变化的键加入updatedKeys集合首个未决更新设置batchPending true并queueMicrotask排定冲刷微任务执行时先对state与updatedKeys做快照保证批内监听器重入更新落到下一轮新集合再以快照为基线调用批监听器prevStateBatch记录上一批基线。测试 batch runs immediately and coalesces state changespackages/ariakit-store/src/test.ts精确验证注册时立即收到一次初始调用随后连续三次setState在冲刷前只计数一次微任务冲刷后收到一次合并后的(state, prevState)。4.5 监听器清理cleanup协议监听函数可以返回清理函数() void。源码中notifyStoreListener第 313-341 行与runInitialListener第 585-626 行共同保证重跑前先清理同一声明在再次触发前先执行上一次返回的清理函数注销时清理registerListener返回的取消函数会执行挂起的 cleanup挂起计数suspendCounts防止重入派发在注册未完成时重复执行同一监听器。CHANGELOG 0.1.4 中记载了对应修复Fixedsyncandbatchto run a listeners pending cleanup before re-registering the same listener测试 sync runs immediately and cleans up before rerun and unsubscribe 等用例即为这些行为的回归保障。五、类型安全编译期的状态契约readme 中的 5 个类型导出构成了 Store 的类型体系测试 preserves the public type surfacepackages/ariakit-store/src/test.ts对它们做了完整的类型级断言expectTypeOfStoreStatetypeof store().toEqualTypeOfSampleState(); expectTypeOfStoreOptionsSampleState, count | label().toEqualTypeOf PartialPickSampleState, count | label (); expectTypeOfStorePropsSampleState[store]().toEqualTypeOf StorePartialSampleState | undefined ();同时测试也通过ts-expect-error验证了非法用法会在编译期报错store.setState(missing, 1); // 非法状态键 store.setState(count, 1); // 非法状态值 store.setState(count, () 1); // 非法 updater 返回值得益于setStateK extends keyof S与StoreStateT的条件类型推导从pick/omit得到的派生 Store 会自动收窄其状态类型PickS, K[number]/OmitS, K[number]派生 Store 上只能读写保留的键。六、测试保障与行为清单packages/ariakit-store/src/test.ts共 2598 行是这套原语的行为契约覆盖了 readme 中每个 API 的关键语义可作为使用时的参考清单行为对应测试用例未知状态键被忽略sets known state keys and ignores unknown keys函数式更新与等值去重sets state with updater functions / does not notify listeners for strict-equal values不可变快照passes immutable state snapshots to listeners键级筛选监听subscribes to selected state changessync 立即执行与清理协议sync runs immediately and cleans up before rerun and unsubscribesync 重入设置状态sync listeners can set state reentrantlybatch 微任务合并batch runs immediately and coalesces state changesbatch 监听器互相派生batch listeners can set state for later batch listeners空键数组只触发一次does not rerun empty-key sync and batch listeners after registration派发中动态注册监听器fires a keyed listener added during the keyed fast path其中 sync listeners can set state reentrantly 演示了sync在 Ariakit 组件中最典型的用法——用一个键驱动另一个键保持一致如open→mountedsync 回调里调用store.setState(mounted, state.open)源码通过仅通知变更键的筛选避免无限循环三次回调按预期顺序收到正确的(prevState, state)组合。七、版本与发布注意事项ariakit/solid-store的变更记录见 packages/ariakit-solid-store/CHANGELOG.md。值得关注的发布信息0.1.0独立工具包正式拆分提供纯 ESM、单一公开入口的 Store 工具同时提供import { createStore } from ariakit/store的直接导入路径0.1.4修复了合并 Store 在初始化期间sync监听器更新父 Store 时的值同步问题涉及Select、Combobox等组合式组件并修复sync/batch重注册前的 pending cleanup 执行顺序0.1.5修复 NaN 键订阅的一致性0.1.6修复发布产物遗漏构建输出的问题0.1.10跟随ariakit/store0.1.10更新依赖。由于 readme 明确声明该包不遵循语义化版本破坏性变更可能出现在任意 patch/minor 版本中建议锁定精确版本号并在升级时以 packages/ariakit-solid-store/CHANGELOG.md 为变更依据。结语ariakit/solid-store虽然只是一个再导出层但它定义了 Solid 侧访问 Ariakit Store 原语的唯一入口。真正值得深入的是其背后的ariakit/store实现不可变快照、键级监听快速路径、subscribe/sync/batch三种时机语义、父 Store 双向同步与防环修复、微任务批量合并、监听器清理协议以及完整的状态类型推导。理解这套契约你就能在 Solid 应用中自由组合createStore、pick、omit、mergeStore搭建出与 Ariakit 组件体系一致、类型安全且行为可预期的状态层。赞分享UI组件前端【免费下载链接】ariakitToolkit with accessible components, styles, and examples for your next web app项目地址https://gitcode.com/gh_mirrors/ar/ariakit点击查看免费下载相关推荐Pwndbg 开发实战指南双调试器架构、命令与配置参数开发、测试与代码规范Pwndbg 开发实战指南双调试器架构、命令与配置参数开发、测试与代码规范 Pwndbg 是一个用 Python 编写的 GDB 与 LLDB 调试增强插件UI组件前端ariakit/store 深入解析Ariakit 框架无关 Store 原语的演进、API 与源码实现ariakit/store 深入解析Ariakit 框架无关 Store 原语的演进、API 与源码实现 导读 ariakit/store 是 AriakUI组件前端Ariakit Store原理详解用3个Hook掌控任何组件的状态Ariakit Store原理详解用3个Hook掌控任何组件的状态 Ariakit Store 是 Ariakit https://link.gitcode.UI组件前端上一篇告别繁琐切换vue-pure-admin分段控制器让界面交互效率提升300%下一篇PyPDF 4.0 深度解析纯Python PDF处理库的架构设计与性能优化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网