type
status
date
slug
summary
tags
category
icon
password
Jotai
Jotai是一个原始且灵活的React状态管理库。
- 原始:提供了Hooks方式的Api,使用方式类似于
useState
,useReducer
- 灵活:可以组合多个
Atom
来创建新的Atom
,并且支持异步
- 高性能:更新对应的
Atom
只会重新渲染订阅了这个Atom
的组件,并不会像Context
那样导致整个父组件重新渲染,所以可以做到精确渲染
定义 Atom
atom: 原子的意思。一个Atom代表一个状态,使用atom函数创建一个Atom,需要传入一个参数作为初始值。
使用 Atom
useAtom
函数接受一个参数,参数值为 一个
Atom返回值是一个数组
数组第一个值是
Atom
存储的值,第二个值是更新 Atom
值的函数创建派生原子,有三种模式
定义或者派生使用时,atom()需要放在函数外,否则会造成死循环。
建议维护一份ts文件单独存放定义的原子
1.Read-only atom
2.Write-only atom
3.Read-Write atom
Provider(opens new window)
Provider 是为一个组件子树提供状态。多个 Provider 可以用于多个子树,甚至可以嵌套。这就像 React Context 一样工作。
在不提供Provider的情况下,会使用默认状态,我们可以不提供他来包裹使用Atom的组件。
使用 Provider 带来的效果
- 为每个组件树提供不同的状态
- 包含了一些调试信息
- 接受原子的初始值initialValues
onMount
创建后的原子可以有一个可选的属性
onMount
。onMount
是一个函数,它接受一个setAtom
函数,并可以返回onUnmount
函数:一个卸载的函数(类似于useEffect的使用方法)。onMount
当原子首次在提供程序中使用时调用该函数,不再使用时会触发onUnmount
卸载函数。Utils
内置了很多工具方法 (opens new window),可以根据需要选择使用。更多方法详见上方文档
1.持久化存储的工具方法
2.原子拆解选择器
第三方集成
Jotai内部对多个状态库进行了继承,可根据需要安装对应的包,来配合Jotai使用。
调试工具
Jotai官方提供了两种Debug方式:
- 用Redux DevTools去查看特定
atom
的状态,只需要将atom
和label传进jotai/devtools
中的useAtomDevtools
Hooks。
- 用React Dev Tools去查看Jotai的Provider,全部
atom
的状态都存放在DebugState
中,但需要额外设定atom
的debugLabel作为key,否则会显示为<no debugLabel>
。
Mobx
mobx
一个基于 react 的一个响应式更新状态库,比较好用的一点是,其定义的数据可以类似 state 被 UI 所追踪,用 vue 的类似实现来写 react,视图与逻辑分离,还是挺爽的。
示例
基本概念
- State(状态)
- Actions(动作)
- Derivations(派生)
State
数据状态。
如何将数据标识为 mobx 的state?
通过 makeObservable 并将属性标识为 observable:
Actions
更改数据状态。
可以理解为数据提交,主要是为了防止直接修改数据,使得数据的更改变得难以追踪和维护。
如上面示例中的 toggle 就是一个 action,它通知 finished 变更为 !finished。
Derivations
派生:任何基于 state 进行处理的东西。
主要分为两大类:
1.Computed :计算属性,基于 state 二次加工的数据。
2.Reactions:变化,当 State 改变时需要自动运行的副作用。
示例:
这里将 unfinishedTodoCount 标识为 computed,每当其依赖的状态(即todos)变更时,它也会跟着变更,底层原理:proxy。
Reaction 其实原理与其类似,只不过为了分类将其概念抽离了出来,其不返回数据,只产生副作用,一般用来做什么?如打印到控制台、发出网络请求、增量更新 React 组件树以便更新 DOM 等。
到目前为止,最常用的 reaction 形式是UI组件。
如何自定义 reaction :
通常情况下你不需要使用它们,可以使用
autorun
(opens new window)reaction
(opens new window)或 when
(opens new window)方法来订制你的特殊业务场景。比如,下面的
autorun
将在unfinishedTodoCount
的数量发生变化时输出日志。响应式 React 组件
使用 observer 将 react 组件与 state 相关联。
observable state
一共有三种方式创建可观测状态。
1.makeObservable。捕获监听对象并手动映射注解。
2.makeAutoObservable。加强版的 makeObservable ,会自动映射注解(减低心智,推荐使用)。
3.observable。以函数调用的方式进行注解,将整个对象变为可观测状态。
makeObservable
makeObservable(target, annotations?, options?)
makeAutoObservable
makeAutoObservable(target, overrides?, options?)
自动推断规则:
- 所有 自有 属性都成为
observable
。
- 所有
get
ters 都成为computed
。
- 所有
set
ters 都成为action
。
- 所有 prototype 中的 functions 都成为
autoAction
。
- 所有 prototype 中的 generator functions 都成为
flow
。(需要注意,generators 函数在某些编译器配置中无法被检测到,如果 flow 没有正常运行,请务必明确地指定flow
注解。)
- 在
overrides
参数中标记为false
的成员将不会被添加注解。例如,将其用于像标识符这样的只读字段。
observable
observable(source, overrides?, options?)
使用 proxy 包装对象,并将其变为可观测的。
特性:
- 之后被添加到这个对象中的属性也将被侦测并使其转化为可观察对象(除非禁用 proxy (opens new window))
observable
方法也可以被像 arrays (opens new window),Maps (opens new window)和 Sets (opens new window)这样的集合调用。这些集合也将被克隆并转化为可观察对象。
可用的注解(opens new window)
注解 | 描述 |
observable observable.deep | 定义一个存储 state 的可跟踪字段。如果可能,任何被赋值给 observable 的字段都会基于它自己的类型被(深度)转化为observable 、autoAction 或 flow 。只有 plain object 、array 、Map 、Set 、function 、generator function 可以转换,类实例和其他实例不会被影响。 |
observable.ref | 类似于 observable ,但只有重新赋值才会被追踪。所赋的值会被完全忽略,并且将不会主动转化为 observable /autoAction /flow 。比方说,在你打算将不可变数据存储在可观察字段中时,可以使用这个注解。 |
observable.shallow | 类似于 observable.ref 但是是用于集合的。任何所赋的集合都会被转化为可观察值,但是其内部的值并不会变为可观察值。 |
observable.struct | 类似于 observable ,但是会忽略所赋的值中所有在结构上与当前值相同的值。 |
action | 把一个函数标记为会修改 state 的 action。查看 actions (opens new window)获取更多信息。不可写。 |
action.bound | 类似于 action,但是会将 action 绑定到实例,因此将始终设置 this 。不可写。 |
computed | 可以用在 getter (opens new window)上,用来将其声明为可缓存的派生值。查看 computeds (opens new window)获取更多信息。 |
computed.struct | 类似于 computed ,但如果重新计算后的结果在结构上与之前的结果相等,那么观察者将不会收到通知。 |
true | 推断最佳注解。查看 makeAutoObservable (opens new window)获取更多信息。 |
false | 刻意不为该属性指定注解。 |
flow | 创建一个 flow 管理异步进程。查看 flow (opens new window)获取更多信息。需要注意的是,推断出来的 TypeScript 返回类型可能会出错。 不可写。 |
flow.bound | 类似于 flow, 但是会将 flow 绑定到实例,因此将始终设置 this 。 不可写。 |
override | |
autoAction | 不应被显式调用,但 makeAutoObservable 内部会对其进行调用,以便根据调用上下文将方法标识为 action 或者派生值。 |
局限性(opens new window)
详细见文档链接。
Options(opens new window)
上面的 API 都有一个可选的
options
参数,该参数是一个对象,支持以下选项:autoBind: true
默认使用action.bound
/flow.bound
,而不使用action
/flow
。不影响被显式注释过的成员。
deep: false
默认使用observable.ref
,而不使用observable
。不影响被显式注释过的成员。
name: <string>
为对象提供一个调试名称,该名称将被打印在错误消息和 reflection API 中。
proxy: false
迫使observable(thing)
使用非 proxy (opens new window)的实现。如果对象的结构不会随着时间变化,那么这就是一个很好的选择,因为非代理对象更容易调试并且速度更快。请参见 避免代理 (opens new window)。
将 observable 转换回普通的 JavaScript 集合
有时有必要将可观察的数据结构转换回原生的数据结构。 例如,将可观察对象传入一个无法跟踪可观察对象的 React 组件时,或者想要获取一个不会再被更改的副本时。
要进行浅转换,用常用的 JavaScript 操作就可以做到:
actions
使用 action 注解来修改 state 状态。
使用 action 包装函数
action 不仅仅是一个注解,更是一个高阶函数,因此一切涉及修改 state 的操作,都应该将其标记为 action。action 不会触发 state 的追踪。
示例:
action 绑定
为了解决 this 的指向问题,可以使用
action.bound
使 this 始终正确的绑定在函数内部。(使用
makeAutoObservable(o, {}, { autoBind: true })
会自动绑定所有的 actions 和 flows)临时执行的 action
可以使用
runInAction(fn)
来立即执行一个临时定义的 action。这在异步进程中非常有用。异步执行 action
任何的 state 的更改操作都应该处于 action 作用域中,因此,当处理 Promise 时,涉及 state 修改的操作都应当标识为action。
async / await 的替代方案
async / await 作为现代 js 中常用的异步方案,在 mobx 中不推荐使用,而是推荐 flow 作为替代。
Why? 因为 async / await 的本质是 promise 的包装,如上面的 fetchProjects,就是把await后的代码放到Promise.then()函数里,把catch部分的代码放到.catch的回调函数里,还是同样需要将异步的执行部分用 action 来包装起来,说白了,其本质还是一个 Promise 任务
使用 flow 进行替代
实现:generator 迭代器
flow.bound
用法与
action.bound
类似,使this
始终被正确绑定在函数内部。computeds
基于 state 二次处理数据。
标识:computed,或使用 makeAutoObservable 自动将所有的 getters 标识为 computed。
示例

最佳实践
- 它们不应该有副作用或者更新其他可观察对象。
- 避免创建和返回新的可观察对象。
- 它们不应该依赖非可观察对象的值
Options {🚀}
name
equals
默认设置为
comparer.default
。它充当一个比较函数,用于比较上一个值和下一个值。如果该函数认为两个值相等,那么观察者们将不会被重新计算。内置 comparers
MobX 提供了四种内置的
comparer
方法,这些方法满足 computed
的 equals
选项的大多数需求:comparer.identity
使用全等 (===
)运算符确定两个值是否相同。
comparer.default
与comparer.identity
相同,但是其认为NaN
等于NaN
。
comparer.structural
执行深层的结构比较以确定两个值是否相同。
comparer.shallow
执行浅层的结构比较以确定两个值是否相同。
示例结构比较{👀}
当返回一个新的对象时,即时值一样,观察者们也会默认数据不一样,并会触发监察者。
如果需要进行结构比较可以使用
computed.struct
。keepAlive`
使计算属性的值在未被观察时被暂时停用。
reactions
reactions 是 mobx 中相当重要的概念。和 computed 一样都会在 state 依赖变更时触发,但不同的是
reactions 不处理数据,而是基于数据更改而产生的副作用。
类似于 useEffect 勾子。
autorun
依赖变更时执行,创建时也会执行一次。
示例:
reaction
类似于 autorun,但可以指定追踪的依赖。
reaction(() => value, (value, previousValue, reaction) => { sideEffect }, options?)
- 第一个值:监听的依赖;
- 第二个值:一个函数;参数分别为当前依赖值,依赖变更前的值,reaction;
- 第三个值:options 选项。
when
判断依赖,在指定依赖达到条件时执行。
when(predicate: () => boolean, effect?: () => void, options?)
when(predicate: () => boolean, options?): Promise
示例:
异步
如果你没有提供
effect
函数,when
将会返回一个 Promise
。这样会跟 async / await
很好地结合在一起,让你可以等待可观察对象中的变化。如果要提前取消
when
,可以对它返回的 Promise 调用 .cancel()
函数。规则
- 默认情况下,如果可观察对象发生了改变,受其影响的 reactions 会立即(同步)运行。然而,它们直到当前最外层的 (trans)action 执行结束后才会运行。
- autorun 只会跟踪给定函数在同步执行过程中所读取的可观察对象,不会跟踪异步发生的变化。
- autorun 不会跟踪被其调用的 action 所读取的可观察对象,因为 action 始终不会被追踪。
优化
由于 reactive 会一直监听并等待依赖变更,所以强烈推荐在不需要执行副作用时将它们注销掉。
reactive 会返回一个函数,执行它,销毁 reactive 。
防止内存泄漏:
options
name
该字符串在 Spy event listeners (opens new window)和 MobX developer tools (opens new window)中用作此 reaction 的调试名称。
fireImmediately
布尔值,指示在第一次运行 data 函数后立即触发 effect 函数。默认为
false
delay
对 effect 函数进行节流的毫秒数。如果是 0(默认值),将不进行节流操作。
timeout
设置一段有限的
when
将要等待的时间。如果过了时限,when
将会 reject 或抛出错误。onError
异常行为处理
scheduler
(autorun, reaction)
设置一个自定义调度程序,用来决定应该如何调度 autorun 函数的重新运行。它接受一个函数作为参数,这个函数应该在将来 某个时刻被调用,例如:
{ scheduler: run => { setTimeout(run, 1000) }}
。equals
默认情况下设置为
comparer.default
。如果指定,则此比较函数用于比较 data 函数产生的上一个值和下一个值。当且仅当 此函数返回 false 时,effect 函数才会被执行。- 作者:慕雨
- 链接:https://www.axin.work/article/ececddd7-f020-4c53-9c81-8afb82c60827
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。