简单、可扩展的状态管理
MobX区分了应用程序中的一下三个概念:
1.State(状态)
2.Actions(动作)
3.Derivations(派生)
State(状态)是驱动你的应用程序的数据
通常来说。状态有领域特定状态(比如Todo List中的列表项数据)和视图状态(比如当前选中的列表元素)。
State就像保存着数据的电子表格单元格一样。
将State存储在任何您喜欢的数据结构中:普通对象、数组、类、循环数据结构或引用。这与MobX的工作方式无关。
只要确保所有你想随时间改变的属性都被标记为observable,这样MobX就可以跟踪它们。
import { makeObservable, observable, action } from "mobx"
class Test {
id = Math.random()
tests = false
constructor(title) {
makeObservable(this, {
tests: observable,
trigger: action
})
}
trigger() {
this.tests = !this.tests
}
}
使用 observable就像将对象的属性放在Excel表格的单元格中。但是和单元格不同的是,他们的值不仅仅是数值,也可以是引用、对象和数组。
接下来我们看一下被我们标记为 action 的 trigger
Action(动作)是任意可以改变State(状态)的代码,比如用户事件处理、后端推送数据处理、调度器事件处理等等。
Action就像用户在Excel单元格中输入了新的值。
在test类中,我们可以看到trigger 方法改变了tests属性的值,而tests是被标记为observable的。建议您将所有修改observable值的代码标记为action。MobX可以自动进行事务处理以轻松实现最佳性能。
使用Action可以帮助您更好地组织代码,并防止您在无意中修改State。
在MobX术语中,可以修改State的方法被称为action(动作)。这与基于当前状态来生成新信息的view(视图) 是不同的。您代码中的每一个方法只应完成上述两个目标中的一个。
任何来源是State(状态) 并且不需要进一步交互的东西都是 Derivation(派生)。
属性,完整的对象,数组,Maps 和 Sets 都可以被转化为可观察对象。 使得对象可观察的基本方法是使用 makeObservable 为每个属性指定一个注解。 最重要的注解如下:
1. observable 定义一个存储 state 的可追踪字段。
2. action 将一个方法标记为可修改 state 的 action。
3. computed 标记一个可以由 state 派生出新的值并且缓存其输出的 getter。
像数组,Maps 和 Sets 这样的集合都将被自动转化为可观察对象。
用法:
makeObservable(target, annotations?, options?)
这个函数可以捕获已经存在的对象属性并且使得它们可观察。 任何JavaScript对象(包括类的实例)都可以作为target被传递给这个函数。 一般情况下,makeObservable是在类的构造函数中调用的,并且它的第一个参数是this。annotations参数将会为每一个成员映射注解。需要注意的是,当使用装饰器时,annotations 参数将会被忽略。
派生数据并且接受参数的方法(例如:findUsersOlderThan(age: number): User[] )不需要任何注解。当我们从一个reactuib中调用它们时,它们的读取操作仍然会被跟踪,但是为了避免内存泄漏,它们的输出将不会被记忆化。
import { makeObservable, observable, computed, action } from "mobx"
class Doubler {
value
constructor(value) {
makeObservable(this, {
value: observable,
double: computed,
increment: action,
})
this.value = value
}
get double() {
return this.value * 2
}
increment() {
this.value++
}
}
使用:
makeAutoObservable(target, overrides?, options?)
makeAutoObservable就像是加强版的makeObservable,在默认情况下它将推断所有的属性。你仍然可以使用overrides重写某些注解的默认行为。具体来说,false可以用于从自动处理中排除一个属性或方法。查看上面的代码分页获取示例。与使用makeObservable相比,makeAutoObservable函数更紧凑,也更容易维护,因为新成员不需要显示地提及。然而,makeAutoObservable不能被用于带有super的类或子类。
推断规则:
1. 所有自有属性都成为 observable。
2. 所有 getters 都成为 computed。
3. 所有 setters 都成为 action。
4. 所有 protitype 中的 function 都成为 autoAction。
5. 在 overrides 参数中标记为 false 的成员将不会被添加注解。 例如,将其用于像标识符这样的只读字段。
用法:
observable(source, overrides?, options?)
observable注解可以作为一个函数进行调用,从而一次性将整个对象变成可观察的。source 对象将会被克隆并且所有的成员都将会成为可观察的,类似于 makeAutoObservable 做的那样。 同样,你可以传入一个 overrides对象来为特定的成员提供特定的注解。 查看上面的代码获取示例。
由 observable 返回的对象将会使用 Proxy 包装,这意味着之后被添加到这个对象中的属性也将被侦测并使其转化为可观察对象(除非禁用 proxy )。
observable 方法也可以被像 arrays,Maps 和 Sets 这样的集合调用。这些集合也将被克隆并转化为可观察对象。
上面的 API 都有一个可选的 options 参数,该参数是一个对象,支持以下选项:
1. autoBind: true 默认使用 action.bound / flow.bound, 而不使用 action / flow。不影响被显式注释过的成员。
2. deep: false 默认使用observable.ref, 而不使用observable。不影响被显式注释过的成员。
3. name: <string> 为对象提供一个调试名称,该名称将被打印在错误消息和 reflection API 中。
4. proxy: false 迫使 observable(thing) 使用非 proxy 的实现。如果对象的结构不会随着时间变化,那么这就是一个很好的选择,因为非代理对象更容易调试并且速度更快。
用法:
1. action
2. action(fn)
3. action(name, fn)
所有的应用程序都有 actions。action 就是任意一段修改 state 的代码。原则上,actions 总会为了对一个事件做出响应而发生。例如,点击了一个按钮,一些输入被改变了,一个 websocket 消息被送达了,等等。
尽管 makeAutoObservable 可以自动帮你声明一部分 actions,但是 MobX 还是要求你声明你的 actions。Actions 可以帮助你更好的组织你的代码并提供以下性能优势:
1. 它们在 transactions 内部运行。任何可观察对象在最外层的 action 完成之前都不会被更新,这一点保证了在 action完成之前,action 执行期间生成的中间值或不完整的值对应用程序的其余部分都是不可见的。
2. 默认情况下,不允许在 actions 之外改变 state。这有助于在代码中清楚地对状态更新发生的位置进行定位。
action 注解应该仅用于会修改 state 的函数。派生其他信息(执行查询或者过滤数据)的函数不应该被标记为 action,以便 MobX 可以对它们的调用进行跟踪。带有 action 注解的成员是不可枚举的。
import { makeAutoObservable } from "mobx"
class Doubler {
value = 0
constructor(value) {
makeAutoObservable(this)
}
increment() {
this.value++
this.value++
}
}
action
包装函数为了尽可能地利用 MobX 的事务性,actions 应该尽可能被传到外围。如果一个类方法会修改state,可以将其标记为 action。把时间处理函数标记为 actions 就更好了,因为最外层的事务起着决定性作用。一个未被标记的、会接着调用两个 action 的时间处理函数仍然将会生成两个事务。
为了帮助创建基于 action 的事件处理函数,action 不仅仅是一个注解,更是一个高阶函数。可以使用函数将它作为一个参数来调用,在这种情况下它将会返回一个有着相同签名的使用 action 包装过的函数。
例如在 React 中,可以按照下面的方式包装 onClick 事件处理函数。
const ResetButton = ({ formState }) => (
<button
onClick={action(e => {
formState.resetPendingUploads()
formState.resetValues()
e.stopPropagation()
})}
>
Reset form
</button>
)
为了更好的调试体验,我们推荐为被包装的函数命名,或者将名称作为 action 的第一个参数进行传递。
用法:
action.bound (注解)
action.bound 注解可用于将方法自动绑定到正确的实例,这样 this 会始终被正确绑定在函数内部。
使用:
1. computed
2.computed(options)
3.computed(fn, options?)
计算值可以用来从其他可观察对象中派生信息。 计算值采用惰性求值,会缓存其输出,并且只有当其依赖的可观察对象被改变时才会重新计算。 它们在不被任何值观察时会被暂时停用。
例子
计算值可以通过在 JavaScript getters 上添加 computed 注解来创建。使用 makeObservable 将getter 声明为 computed 或者如果你希望所有的 getters 被自动声明为 computed,可以使用makeAutoObservable,observable 或者 extendObservable。
下面的示例依靠 Reactions 高级部分中的 autorun 来辅助说明计算值的意义。
import { makeObservable, observable, computed, autorun } from "mobx"
class OrderLine {
price = 0
amount = 1
constructor(price) {
makeObservable(this, {
price: observable,
amount: observable,
total: computed
})
this.price = price
}
get total() {
console.log("Computing...")
return this.price * this.amount
}
}
const order = new OrderLine(0)
const stop = autorun(() => {
console.log("Total: " + order.total)
})
// Computing...
// Total: 0
console.log(order.total)
// (不会重新计算!)
// 0
order.amount = 5
// Computing...
// (无需 autorun)
order.price = 2
// Computing...
// Total: 10
stop()
order.price = 3
// 计算值和 autorun 都不会被重新计算.
上面的例子很好地展示了计算值的好处,它充当了缓存点的角色。 即使我们改变了 amount,进而触发了 total 的重新计算, 也不会触发 autorun,因为 total 将会检测到其输出未发生任何改变,所以也不需要更新 autorun。
相比之下,如果 total 没有被注解,那么 autorun 会把副作用运行 3 次, 因为它将直接依赖于 total 和 amount。