Effect Atom 怎么把状态持久化到 localStorage?

用户把 localStorage 里的 ui.theme 手工改成了 purple,你的应用读出来,照单全收,然后渲染出一个不存在的主题。

用 Atom.kvs:它通过 KeyValueStore 服务读写,并用 Schema 做编解码,读回来的值一定是 Schema 允许的值,存储后端是一个可替换的 Layer。

价值不在于少写几行,而在于边界:存储里读出来的是不可信的外部数据。Atom 的基本概念,以及普通 atom 无人订阅就会被回收的行为,见 用 Effect 写 React 前端,状态该怎么管?。下面讲具体写法和我验证过的行为。

Atom.kvs 怎么写?

// theme.ts
import { Schema } from "effect"
import { Atom } from "effect/reactivity"
import { BrowserKeyValueStore } from "@effect/platform-browser"

const runtime = Atom.runtime(BrowserKeyValueStore.layerLocalStorage)

export const themeAtom = Atom.kvs({
  runtime,
  key: "ui.theme",
  schema: Schema.Literals(["light", "dark"]),
  defaultValue: () => "light" as const
})

这需要再装一个 @effect/platform-browser@4.0.5。注意 defaultValue 是一个返回默认值的函数,不是裸值,这是和 v3 写法不同的地方。

读写的结果是什么?

我在 jsdom 的 localStorage 上验证:首次读取是默认值 light,设置为 dark 之后,存储里的原始字符串是 "dark",读回也是 dark。

为什么不直接用 localStorage?

Schema 保证读回来的一定是 light 或 dark,被手工改坏的值不会渗进应用。直接用裸 localStorage,读出的数据就是未经校验的字符串,校验得自己到处补。Schema 在边界做解码的思路,见 Effect Schema:在系统边界做解码,一份定义多处使用。

想换成 sessionStorage 或 IndexedDB 要改什么?

存储后端是通过 Atom.runtime 传入的 Layer,所以换存储只改传给 Atom.runtime 的那一行 Layer,atom 的定义不动。

版本是 effect@4.0.5 与 @effect/platform-browser@4.0.5。默认值与 dark 的读写在 jsdom 的 localStorage 上跑过。「手工改坏的值不会渗进应用」是我根据 Schema 解码的作用做的推断,没有单独用坏值测过;换成 sessionStorage 或 IndexedDB 也没有实际运行,这两个具体的 Layer 名字我没有验证。