ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

@effect/atom-solid 实战指南:在 SolidJS 中接入 Effect Atom 响应式状态管理

@effect/atom-solid 实战指南:在 SolidJS 中接入 Effect Atom 响应式状态管理 effect/atom-solid 实战指南在 SolidJS 中接入 Effect Atom 响应式状态管理【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3codeeffect/atom-solid是 Effect 官方为 SolidJS 提供的原子状态管理绑定层它让 Effect 的 Atom 响应式模块能够以 Solid 原生的 signal 语义运行在组件与计算上下文中。本文以该包为骨架结合其源码实现Hooks.ts、RegistryContext.ts与测试用例讲解安装方式、注册表上下文、全套 hooks 的用法与底层原理帮助你在 SolidJS 项目中用 Effect 的类型安全能力组织全局与局部状态。一、包定位Atom 的 SolidJS 桥接层effect/atom-solid隶属于 Effect 项目packages/atom/下的前端绑定套件另有effect/atom-react与effect/atom-vue其定位与 包级 README 的描述一致SolidJS bindings for Atom, the reactive state management modules for Effect.也就是说本包不重新实现状态管理而是将effect/unstable/reactivity下的Atom、AtomRef、AsyncResult、AtomRegistry等模块以 Solid 的createSignal、createComputed、createEffect、createResource、createContext等原语包装成 hooks。从 src/index.ts 可以看到包的全部公开 API 只有两个来源export * from ./Hooks.ts—— 全部 hook 函数export * from ./RegistryContext.ts—— 注册表上下文与 Provider。版本与依赖关系package.json 揭示了它的运行环境约束项值包名effect/atom-solid版本4.0.0-rc.112与 effect 同步发布类型ESMtype: module许可证MITpeerDependencieseffect: workspace:^、solid-js: 1.9.14 2.0.0devDependenciessolid-js^1.9.15、solidjs/testing-library^0.8.10、jsdom^30.0.1等注意两个约束其一solid-js需要1.9.14 及以上、2.0 以下其二它依赖effect的 RC 版本线4.0.0-rc 系列尚未进入正式发布属于 unstable 状态。二、安装与 effect 一起按 RC 通道引入原文档给出的安装命令即是最佳实践npm install effectrc effect/atom-solidrc说明两点必须同时安装effectrc因为本包所有底层模块Atom、AtomRegistry、AsyncResult等都从effect/unstable/reactivity导入Hooks.ts 的 import 语句可以佐证import * as Cause from effect/Cause import * as Effect from effect/Effect import * as Exit from effect/Exit import * as AsyncResult from effect/unstable/reactivity/AsyncResult import * as Atom from effect/unstable/reactivity/Atom import * as AtomRegistry from effect/unstable/reactivity/AtomRegistryrc标签确保安装的 effect 与effect/atom-solid保持同一 RC 版本线当前为 4.0.0-rc.112避免因 peerDependencies 不满足而导致运行时 API 不匹配。若使用 pnpm workspace 开发则直接通过effect: workspace:^引用同仓库源码本仓库即以此方式组织源码直接以./src/*.ts形式对外导出。三、核心抽象AtomRegistry 与 RegistryContext所有 hooks 都不是直接操作 Atom 本身而是通过当前 Solid 上下文中的AtomRegistry读写 Atom。这是理解本包的关键。RegistryContext默认注册表RegistryContext.ts 中定义了export const RegistryContext createContextAtomRegistry.AtomRegistry(AtomRegistry.make())当组件树中不存在任何 Provider 时所有 hooks 会共享这个独立的默认注册表注册表负责存储 atom 值、调度更新任务schedule work以及清理不再使用的 atom。RegistryProvider为子树创建独立注册表export const RegistryProvider (options: { readonly children?: JSX.Element | undefined readonly initialValues?: Iterablereadonly [Atom.Atomany, any] | undefined readonly scheduleTask?: ((f: () void) () void) | undefined readonly timeoutResolution?: number | undefined readonly defaultIdleTTL?: number | undefined }) { ... }其实现RegistryContext.ts做了三件事用AtomRegistry.make({ ... })创建注册表并透传initialValues、scheduleTask、timeoutResolution、defaultIdleTTLdefaultIdleTTL默认值为400毫秒即 atom 空闲 400ms 后才会被注册表回收onCleanup(() registry.dispose())—— 当 Provider 所属的 Solid owner 被销毁时注册表随之 dispose实现原子状态的自动清理。两个使用注意点源码 JSDoc 明确标注Provider 选项在注册表创建时一次性消费不是响应式更新。若依赖响应式值需在变化时重建 Provider自定义scheduleTask必须返回一个在 Solid cleanup 期间可安全调用的取消函数。测试用例 index.test.tsx 演示了useAtomInitialValues与RegistryContext.Provider配合的用法同一个注册表内后一次对同一 atom 的初始值设置会被忽略。四、Hooks 全景读取、写入、挂载与订阅Hooks.ts 实现了 9 个 hooks覆盖了 Solid 组件与 Effect Atom 交互的全部场景。下表先给出总览再逐个讲解Hook职责核心实现原语useAtomValue订阅 atom 值返回 AccessorcreateSignalcreateComputedregistry.subscribeuseAtom同时返回值的 Accessor 与 setteruseAtomValueregistry.setuseAtomSet只返回 setter不订阅值registry.setuseAtomMount仅挂载 atom不读不写registry.mountuseAtomRefresh挂载 atom 并返回刷新回调registry.refreshuseAtomSubscribe订阅回调函数createEffectregistry.subscribeuseAtomInitialValues播种初始值每注册表一次registry.ensureNode().setValueuseAtomResource将AsyncResultatom 转为 Solid ResourcecreateResourceuseAtomRef/useAtomRefProp/useAtomRefPropValue订阅AtomRef值/属性createSignalr.subscribe4.1 useAtomValue订阅 atom 值export const useAtomValue: { A(atom: () Atom.AtomA): AccessorA A, B(atom: () Atom.AtomA, f: (_: A) B): AccessorB }注意入参是一个返回 Atom 的 thunk而非 Atom 本身这是为了在 Solid 的响应式环境中支持「切换 atom」——当 thunk 依赖的 signal 变化时订阅会自动迁移到新 atom。底层实现Hooks.ts非常精巧function createAtomAccessorA(registry, atom: () Atom.AtomA): AccessorA { const [value, setValue] createSignalA(null as any) createComputed(() { onCleanup(registry.subscribe(atom(), setValue as any, constImmediate)) }) return value }用createSignal承载 atom 的当前值在createComputed内部订阅immediate: true保证首次立即推送通过onCleanup在 atom 切换或 owner 销毁时自动退订。测试index.test.tsx覆盖了三种核心行为读取普通 atomAtom.make(42)、传入映射函数map: (value) value * 2得到 84、以及registry.set(atom, updated)后组件自动更新还有对 computed atomAtom.make((get) get(baseAtom) * 2)的支持。4.2 useAtom读 写的组合export const useAtom R, W, const Mode extends value | promise | promiseExit never( atom: () Atom.WritableR, W, options?: { readonly mode?: ... } ): readonly [value: AccessorR, write: ...]返回值是「值访问器 写入函数」的元组。写入函数支持两种形式Hooks.ts(value: W | ((value: R) W)) void即既可以写入新值也可以传入更新函数基于当前值派生。测试演示了链式更新const [value, setValue] useAtom(() atom) setValue(1) setValue((current) current 1) // 最终值为 2测试断言最终观察到的值为 2index.test.tsx。4.3 mode 选项value / promise / promiseExit对类型为AsyncResult的 atomuseAtom与useAtomSet支持mode选项让 setter 返回 Promisemode: value默认同步 settermode: promise写入后返回PromiseSuccess内部用Effect.runPromiseExit驱动注册表计算并在失败时通过Cause.squash抛出错误Hooks.tsmode: promiseExit返回PromiseExitSuccess, Failure保留完整的失败信息而不抛异常。类型层面Mode只有在该 atom 的值确实是AsyncResult时才允许选择promise/promiseExit否则被约束为value属于典型的依赖类型级约束。4.4 useAtomSet / useAtomMount / useAtomRefreshuseAtomSet只写不读。内部先mountAtom再返回 setter适合「仅负责更新、由别处负责展示」的场景useAtomMount只挂载不读写。适用场景是「保持某个 atom 处于 mounted 状态」例如触发它的副作用或保证其计算持续存活Hooks.tsuseAtomRefresh挂载并返回() registry.refresh(memo())回调用于显式刷新如重新执行异步加载。三者都通过mountAtom挂载function mountAtomA(registry, atom: () Atom.AtomA): void { createComputed(() { onCleanup(registry.mount(atom())) }) }4.5 useAtomSubscribe订阅回调export const useAtomSubscribe A( atom: () Atom.AtomA, f: (_: A) void, options?: { readonly immediate?: boolean } ): void用createEffectonCleanup(registry.subscribe(...))实现适合在组件中驱动副作用写日志、触发外部 API 等而非渲染值。4.6 useAtomInitialValues播种初始值export const useAtomInitialValues (initialValues: Iterablereadonly [Atom.Atomany, any]): void { ... }实现要点Hooks.ts在模块级维护WeakMapAtomRegistry, WeakSetAtom记录每个注册表内已播种过的 atom 集合对每个[atom, value]若该 atom 尚未在本注册表播种过则registry.ensureNode(atom).setValue(value)后续对同一注册表中同一 atom 的播种调用会被静默忽略幂等。「当注册表已存在时从组件内补种初始值」是它的典型用途。4.7 useAtomResourceAsyncResult → Solid Resourceexport const useAtomResource A, E( atom: () Atom.AtomAsyncResult.AsyncResultA, E, options?: ResourceOptionsA { readonly suspendOnWaiting?: boolean } ): ResourceReturnA, void这是本包与 Solid Suspense 生态集成度最高的 hook。它先用useAtomValue订阅AsyncResultatom再交给createResource做三态映射Hooks.tsAsyncResult 状态Resource 行为Initial或suspendOnWaiting: true时的waiting返回永不 resolve 的 PromiseconstUnresolvedPromise使 resource 保持loading可被Suspense捕获Successresolve 出result.valueFailure通过Cause.squash(result.cause)reject测试index.test.tsx验证了Initial状态下resource.loading true且值为undefined。五、AtomRef 系列不经过注册表的直接订阅AtomRef是与注册表解耦的引用式 atomuseAtomRef系列 hooks 让 Solid 组件可以直接从AtomRef.ReadonlyRef渲染useAtomRef(ref)读取ref().valueref.subscribe(setValue)订阅切换 ref 或 owner 销毁时onCleanup退订Hooks.tsuseAtomRefProp(ref, prop)通过createMemo(() ref().prop(prop))返回属性级的子 refuseAtomRefPropValue(ref, prop)把前两者组合直接返回属性值的 Accessor。一个值得注意的坑源码 JSDoc 明示prop参数是普通值捕获不会响应式更新。如果属性键可能变化必须重建 hook 调用例如把prop放进外层响应式分支。测试对三者逐一验证index.test.tsxref.set(1)后useAtomRef观察到 1对{ count: 0, label: a }的对象 refref.set({ count: 1, ... })后useAtomRefProp/useAtomRefPropValue均更新到 1。六、生命周期与清理机制从源码可以总结出本包遵循的 Solid 生命周期约定订阅自动清理useAtomValue、useAtomRef在createComputed内订阅并通过onCleanup释放当 thunk 依赖变化导致 atom 切换时旧订阅先被清理再建立新订阅挂载自动回收useAtomMount、useAtomSet、useAtomRefresh通过mountAtom在 owner 生命周期内持有 atom注册表随 Provider 销毁RegistryProvider在onCleanup中调用registry.dispose()整个子树内的 atom 状态被回收空闲回收defaultIdleTTL默认 400ms未被订阅的 atom 会被注册表按空闲策略回收。这也解释了为什么所有 hook 都要求传入 thunk() Atom而不是 Atom 实例——只有这样才能让注册、订阅、清理逻辑完全纳入 Solid 的响应式依赖追踪。七、测试与运行环境包的测试设施vitest.setup.ts由三部分组成testing-library/jest-dom/vitest提供 DOM 断言solidjs/testing-library的cleanup()在每个用例后重置 DOMjsdom提供浏览器环境模拟。测试全部在createRoot内执行如 renderAtomValue 辅助函数所示以正确建立 Solid owner 树涉及自定义注册表时用createComponent(RegistryContext.Provider, { value: registry, ... })注入上下文。若要在自己的项目里为依赖 atom hooks 的组件编写测试可直接复用这一模式。八、典型集成场景速览全局状态在应用根部使用默认RegistryContext不套 Provider所有组件共享同一个默认注册表局部状态域用RegistryProvider为某个子树创建独立注册表配合initialValues播种子树销毁时状态自动回收异步加载把AsyncResultatom 与useAtomResource组合配合 SolidSuspense实现声明式加载态细粒度订阅用useAtomSubscribe把 atom 变化接到副作用管线保持渲染路径纯粹。结语effect/atom-solid是一层「薄而严谨」的桥接它没有重复造状态管理轮子而是把 Effect Atom 的类型安全与 Solid 的原生响应式原语signal、computed、effect、resource、context精确对齐并在生命周期管理上做到与 Solid owner 树完全同步。无论是全量接入 Effect 生态还是只把 Atom 当作「带类型的全局 store」本文所述的 hooks 与 RegistryContext 机制都能作为你项目集成的直接参考。说明以上 API 形态、默认值与行为均以当前仓库 .repos/effect-smol/packages/atom/solid 的源码src/Hooks.ts、src/RegistryContext.ts及测试test/index.test.tsx为准包当前处于4.0.0-rc阶段接口在正式发布前可能调整。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表