跳转到内容

插件与组合

ZhuàngTài 的核心故意很薄。真正让它适应不同场景的,是 creator 插件。你可以把插件看成一层层包在 atom creator 外面的行为壳,它们不会改变核心模型,只是给它加上额外能力。

atom() 是默认导出,它保持未扩展状态,只接收初始值。想挂载插件时,先用 createAtom() 创建一个可扩展的 creator。

import { createAtom } from "@zhuangtai-js/core";
import { persist } from "@zhuangtai-js/persist";
const atom = createAtom().use(persist);
const theme = atom("light", {
persist: {
key: "theme",
},
});

这个设计很直接。基础 atom() 适合纯 core 场景,createAtom() 适合你要加 persistsyncfreezeimmer 的场景。

插件安装在 creator 上,不是安装在 atom 实例上。你可以把多个插件接起来:

import { createAtom } from "@zhuangtai-js/core";
import { persist } from "@zhuangtai-js/persist";
import { sync } from "@zhuangtai-js/sync";
const atom = createAtom().use(persist).use(sync);
const theme = atom("light", {
persist: { key: "theme" },
sync: { key: "theme" },
});

.use() 从左到右建立 creator 层级,后安装的插件位于更外层。因此在这个例子中,sync 是最外层,persist 位于它的内层。本地调用 theme.set("dark") 时,更新先进入 sync 的 wrapper,再通过 context.next() 向内传给 persist,最后到达底层 atom。

层次顺序也决定公共 TypeScript 形态:最外层插件声明的 kind 决定最终 creator 和 atom 暴露的类型。例如 createAtom().use(immer).use(freeze) 最终采用 freeze 的 default kind,因此不会错误暴露 Immer recipe setter。

同一个 plugin ID 不能重复安装;对同一个 creator 重复 .use() 会同步抛出 TypeError,避免选项和 wrapper 语义产生歧义。

收到 sync 的远端广播时,情况会不一样。广播会写入 sync 创建时捕获的内层 state:它会经过安装在 sync 之前、位于其内层的插件(本例中的 persist),但会绕过 sync 自己的广播 set 以及之后安装在它外层的 wrapper。这就是为什么插件顺序和职责边界都很重要。

一个现实例子,主题在多标签页里保持一致

Section titled “一个现实例子,主题在多标签页里保持一致”
const theme = atom("light", {
persist: {
key: "theme",
},
sync: {
key: "theme",
},
});
theme.set("dark");

这个组合适合主题、语言、面板折叠状态这类小而稳的偏好设置。persist 负责跨刷新保存,sync 负责跨标签页同步。两者放在一起,能得到一个很自然的体验:你改一次,别的标签页也会跟着变。

插件 适合什么 典型场景
persist 把状态保存在同步 storage 里 主题、语言、草稿、用户偏好
freeze 开发期抓住原地修改 调试复杂对象、数组状态
immer 用“改草稿”的方式写不可变更新 深层嵌套对象、列表更新
sync 在同源上下文间同步状态 多标签页主题、协作面板、窗口联动

如果你的痛点是“怕自己不小心改坏状态”,先选 freeze。如果你的痛点是“不可变更新写起来太啰嗦”,先选 immer。如果你要在刷新后还保留值,选 persist。如果你要让多个标签页保持一致,选 sync

  • 插件只影响它们被安装的 creator。
  • .use() 越靠后,wrapper 层级越靠外;本地 set() 从外向内传递。
  • 最外层插件的 kind 决定最终公共类型。
  • plugin ID 必须唯一,重复安装会同步失败。
  • sync 的远端更新写入其内层 state;它会经过内层插件,但会绕过 sync 自己和外层插件的 set wrapper。
  • 需要同时持久化并跨 tab 同步时,使用 createAtom().use(persist).use(sync)persist 在内、sync 在外)。反过来时远端更新不会写 storage。
  • 需要 Immer recipe 且开发期冻结时,使用 createAtom().use(freeze).use(immer)immer 在外)。use(immer).use(freeze) 会把函数当成普通 updater 作用在已冻结值上。

这也是为什么 createAtom() 值得单独拿出来。它让你先定好这条状态线要经过哪些能力,再把 atom 实例创建出来。

当前插件发布线 freeze@0.2.ximmer@0.2.xpersist@0.4.xsync@0.2.x 都声明 @zhuangtai-js/core@^0.5.0。框架 adapter 的独立 peer 范围见集成与兼容性

  • 阅读 Core 概念 ,先把 setwatchcomputed 的语义吃透。
  • 阅读 Persist 参考 ,查看 storage 和 codec 的细节。