Effect
jotai-effect 是一个用于响应式副作用的实用程序包。
安装
npm i jotai-effect
atomEffect
atomEffect
是一个用于声明副作用和同步 Jotai 原子的实用函数。它对于观察和响应状态变化非常有用。
参数
type CleanupFn = () => voidtype EffectFn = (get: Getter & { peek: Getter },set: Setter & { recurse: Setter },) => CleanupFn | voiddeclare function atomEffect(effectFn: EffectFn): Atom<void>
effectFn (必需): 一个用于通过 get
监听状态更新和通过 set
写入状态更新的函数。effectFn
对于创建与其他 Jotai 原子交互的副作用非常有用。您可以通过返回一个清理函数来清理这些副作用。
使用
订阅原子变化
import { atomEffect } from 'jotai-effect'const loggingEffect = atomEffect((get, set) => {// 在挂载或某个原子发生变化时运行const value = get(someAtom)loggingService.setValue(value)})
设置和拆解副作用
import { atomEffect } from 'jotai-effect'const subscriptionEffect = atomEffect((get, set) => {const unsubscribe = subscribe((value) => {set(valueAtom, value)})return unsubscribe})
使用原子或钩子挂载
在使用 atomEffect
定义了一个效果后,它可以被集成到另一个原子的读取函数中,或者传递给 Jotai 钩子。
const anAtom = atom((get) => {// 当 anAtom 挂载时,挂载 atomEffectget(loggingEffect)// ...})// 当组件挂载时,挂载 atomEffectfunction MyComponent() {useAtom(subscriptionEffect)// ...}
atomEffect 的行为
清理函数: 在卸载或重新评估之前,会调用清理函数。
示例
atomEffect((get, set) => {const intervalId = setInterval(() => set(clockAtom, Date.now()))return () => clearInterval(intervalId)})抵抗无限循环: 当
atomEffect
通过set
改变它正在观察的值时,不会重新运行。示例
const countAtom = atom(0)atomEffect((get, set) => {// 这不会无限循环get(countAtom) // 在挂载后,计数将为 1set(countAtom, increment)})支持递归: 对于同步和异步的使用场景,都支持使用
set.recurse
进行递归,但在清理函数中不支持。示例
const countAtom = atom(0)atomEffect((get, set) => {// 每秒增加一次计数const count = get(countAtom)const timeoutId = setTimeout(() => {set.recurse(countAtom, increment)}, 1000)return () => clearTimeout(timeoutId)})支持 Peek: 使用
get.peek
读取原子数据,而不订阅更改。示例
const countAtom = atom(0)atomEffect((get, set) => {// 当 countAtom 发生变化时,不会重新运行const count = get.peek(countAtom)})在下一个微任务中执行:
effectFn
在下一个可用的微任务中运行,所有 Jotai 的同步读取评估完成后。示例
const countAtom = atom(0)const logAtom = atom([])const logCounts = atomEffect((get, set) => {set(logAtom, (curr) => [...curr, get(countAtom)])})const setCountAndReadLog = atom(null, async (get, set) => {get(logAtom) // [0]set(countAtom, increment) // 效果在下一个微任务中运行get(logAtom) // [0]await Promise.resolve().then()get(logAtom) // [0, 1]})store.set(setCountAndReadLog)批量同步更新 (原子事务): 对
atomEffect
原子依赖的多个同步更新被批量处理。效果是以最终值作为单个原子事务运行。示例
const enabledAtom = atom(false)const countAtom = atom(0)const updateEnabledAndCount = atom(null, (get, set) => {set(enabledAtom, (value) => !value)set(countAtom, (value) => value + 1)})const combos = atom([])const combosEffect = atomEffect((get, set) => {set(combos, (arr) => [...arr, [get(enabledAtom), get(countAtom)]])})store.set(updateEnabledAndCount)store.get(combos) // [[false, 0], [true, 1]]有条件地运行 atomEffect:
atomEffect
只在其在应用程序中被挂载时才处于活动状态。这可以防止在不需要时进行不必要的计算和副作用。你可以通过卸载它来禁用效果。示例
atom((get) => {if (get(isEnabledAtom)) {get(effectAtom)}})幂等性:
atomEffect
在状态改变时只运行一次,无论它被挂载了多少次。示例
let i = 0const effectAtom = atomEffect(() => {get(countAtom)i++})const mountTwice = atom(() => {get(effectAtom)get(effectAtom)})store.set(countAtom, increment)Promise.resolve.then(() => {console.log(i) // 1})
依赖管理
除了挂载事件外,当任何依赖项的值改变时,效果也会运行。
同步: 在效果的同步评估期间,使用
get
访问的所有原子都会被添加到原子的内部依赖图中。示例
atomEffect((get, set) => {// 当 `anAtom` 的值改变时更新,但当 `anotherAtom` 的值改变时不更新get(anAtom)setTimeout(() => {get(anotherAtom)}, 5000)})异步: 对于异步效果,你应该使用一个中止控制器来取消待处理的获取请求和承诺。
示例
atomEffect((get, set) => {const count = get(countAtom) // countAtom 是一个原子依赖const abortController = new AbortController()(async () => {try {await delay(1000)abortController.signal.throwIfAborted()get(dataAtom) // dataAtom 不是一个原子依赖} catch (e) {if (e instanceof AbortError) {// 这里是异步清理逻辑} else {console.error(e)}}})()return () => {// 当 countAtom 改变时中止abortController.abort(new AbortError())}})清理: 在清理函数中使用
get
访问原子不会将它们添加到原子的内部依赖图中。示例
atomEffect((get, set) => {// 在挂载时运行一次// 当 `idAtom` 改变时不更新const unsubscribe = subscribe((valueAtom) => {const value = get(valueAtom)// ...})return () => {const id = get(idAtom)unsubscribe(id)}})依赖图的重新计算: 每次运行时都会重新计算依赖图。如果在当前运行期间没有监视某个原子,那么它将不会在当前运行的依赖图中。只有被积极监视的原子才被视为依赖项。
示例
const isEnabledAtom = atom(true)atomEffect((get, set) => {// 如果 `isEnabledAtom` 为真,当 `isEnabledAtom` 或 `anAtom` 的值改变时运行// 否则,当 `isEnabledAtom` 或 `anotherAtom` 的值改变时运行if (get(isEnabledAtom)) {const aValue = get(anAtom)} else {const anotherValue = get(anotherAtom)}})
与 useEffect 的比较
组件副作用
useEffect 是一个 React Hook,它让你可以将组件与外部系统同步。
Hooks 是一种函数,让你可以从函数组件中“钩入” React 的状态和生命周期特性。 它们是一种复用,但不集中,有状态的逻辑。 每次调用 hook 都有一个完全独立的状态。 这种隔离可以被称为 组件范围的。 对于将组件 props 和状态与 Jotai 原子同步,你应该使用 useEffect hook。
全局副作用
对于设置全局副作用,选择 useEffect 和 atomEffect 取决于开发者的偏好。 你是更喜欢直接在组件中构建这个逻辑,还是在 Jotai 状态模型中构建这个逻辑,取决于你采用的思维模型。
atomEffects 更适合在原子中建模行为。 它们的范围是存储上下文,而不是组件。 这保证了无论有多少次调用,都只会使用一个效果。
如果你确保 useEffect 是幂等的,那么也可以通过 useEffect hook 实现同样的保证。
atomEffects 与 useEffect 的区别还在于其他几个方面。它们可以直接对原子状态的改变做出反应,对无限循环有抵抗力,并且可以有条件地挂载。
由你决定
useEffect 和 atomEffect 都有自己的优点和应用。你的项目的具体需求和你的舒适程度应该指导你的选择。 总是倾向于那种给你更顺畅、更直观的开发体验的方法。祝你编程愉快!