MobX 的核心
概念
MobX 在你的应用程序中区分以下三种概念
- 状态
- 动作
- 派生值
让我们仔细看看以下这些概念,或者你也可以在MobX 和 React 的 10 分钟入门中交互式地深入了解这些概念,并逐步构建一个简单的待办事项列表应用程序。
有些人可能在下面描述的概念中认出了“信号”的概念。这是正确的,MobX 是一个先驱式的基于信号的状态管理库。
1. 定义状态并使其可观察
状态是驱动你的应用程序的数据。通常,存在领域特定状态,比如待办事项列表,还存在视图状态,比如当前选中的元素。状态就像电子表格单元格,保存着值。
将状态存储在任何你喜欢的數據結構中:普通对象、数组、类、循环数据结构或引用。这对于 MobX 的工作机制来说无关紧要。只需确保所有你想要随时间改变的属性都被标记为observable
,这样 MobX 就可以跟踪它们。
这里有一个简单的例子
import { makeObservable, observable, action } from "mobx"
class Todo {
id = Math.random()
title = ""
finished = false
constructor(title) {
makeObservable(this, {
title: observable,
finished: observable,
toggle: action
})
this.title = title
}
toggle() {
this.finished = !this.finished
}
}
使用observable
就像将对象的属性变成电子表格单元格。但与电子表格不同的是,这些值不仅可以是原始值,还可以是引用、对象和数组。
提示:喜欢类、普通对象或装饰器?MobX 支持多种风格。
这个例子可以使用makeAutoObservable
进行简化,但通过明确地展示,我们可以更详细地展示不同的概念。请注意,MobX 不强制执行对象风格,普通对象也可以使用,装饰器还可以用于更简洁的类。有关更多详细信息,请参阅页面。
但是toggle
呢,为什么我们把它标记为action
?
2. 使用动作更新状态
动作是任何改变状态的代码段。用户事件、后端数据推送、计划事件等等。动作就像一个用户,在电子表格单元格中输入一个新值。
在上面的Todo
模型中,你可以看到我们有一个toggle
方法,它改变了finished
的值。finished
被标记为observable
。建议将任何改变observable
的代码段标记为action
。这样 MobX 就可以自动应用事务,以实现轻松的最佳性能。
使用动作可以帮助你结构化代码,并防止你在不经意间改变状态。在 MobX 术语中,修改状态的方法被称为动作。与视图形成对比,视图是基于当前状态计算新的信息。每个方法最多应该服务于这两个目标中的一个。
3. 创建自动响应状态变化的派生值
任何可以从状态中推导出来的东西,而无需任何进一步的交互,都是一个派生值。派生值以多种形式存在
- 用户界面
- 派生数据,比如剩余的
todos
数量 - 后端集成,例如将更改发送到服务器
MobX 区分两种类型的派生值
- 计算值,它总是可以使用纯函数从当前可观察状态中推导出来
- 反应,当状态发生变化时需要自动发生的副作用(在命令式编程和响应式编程之间架起桥梁)
在使用 MobX 时,人们往往过度使用反应。黄金法则是在你想要根据当前状态创建值时,始终使用computed
。
3.1. 使用计算值建模派生值
要创建计算值,请使用 JS getter 函数get
定义一个属性,并使用makeObservable
将其标记为computed
。
import { makeObservable, observable, computed } from "mobx"
class TodoList {
todos = []
get unfinishedTodoCount() {
return this.todos.filter(todo => !todo.finished).length
}
constructor(todos) {
makeObservable(this, {
todos: observable,
unfinishedTodoCount: computed
})
this.todos = todos
}
}
MobX 将确保unfinishedTodoCount
在添加待办事项或修改其中一个finished
属性时自动更新。
这些计算类似于 MS Excel 等电子表格程序中的公式。它们会自动更新,但只有在需要时才会更新。也就是说,如果有东西对它们的输出感兴趣的话。
3.2. 使用反应建模副作用
为了让用户能够在屏幕上看到状态或计算值的变化,需要一个反应来重新绘制 GUI 的一部分。
反应类似于计算值,但它们不会产生信息,而是产生副作用,比如打印到控制台、发出网络请求、增量更新 React 组件树以修补 DOM 等等。
到目前为止,最常用的反应形式是 UI 组件。请注意,可以从动作和反应中触发副作用。那些具有明确、显式起源的副作用(例如,在提交表单时发出网络请求)应该从相关的事件处理程序中显式触发。
3.3. 响应式 React 组件
如果你正在使用 React,你可以使用observer
函数(来自你在安装过程中选择的绑定包)来使你的组件具有响应性。在本例中,我们将使用更轻量级的mobx-react-lite
包。
import * as React from "react"
import { render } from "react-dom"
import { observer } from "mobx-react-lite"
const TodoListView = observer(({ todoList }) => (
<div>
<ul>
{todoList.todos.map(todo => (
<TodoView todo={todo} key={todo.id} />
))}
</ul>
Tasks left: {todoList.unfinishedTodoCount}
</div>
))
const TodoView = observer(({ todo }) => (
<li>
<input type="checkbox" checked={todo.finished} onClick={() => todo.toggle()} />
{todo.title}
</li>
))
const store = new TodoList([new Todo("Get Coffee"), new Todo("Write simpler code")])
render(<TodoListView todoList={store} />, document.getElementById("root"))
observer
将 React 组件转换为它们渲染数据的派生值。在使用 MobX 时,没有智能组件或哑组件。所有组件都以智能方式渲染,但以哑方式定义。MobX 只需确保组件始终在需要时重新渲染,而且绝不会多余地渲染。
因此,上面的例子中的onClick
处理程序将强制相应的TodoView
组件重新渲染,因为它使用toggle
动作,但只会导致TodoListView
组件在未完成的任务数量发生变化时才重新渲染。如果你删除“剩余任务”行(或将其放入一个单独的组件中),那么TodoListView
组件在勾选任务时将不再重新渲染。
要了解更多关于 React 如何与 MobX 协作的信息,请查看React 集成部分。
3.4. 自定义反应
您很少会用到它们,但可以使用 autorun
、reaction
或 when
函数来创建它们,以适应您的具体情况。例如,以下 autorun
在 unfinishedTodoCount
数量发生变化时打印一条日志消息
// A function that automatically observes the state.
autorun(() => {
console.log("Tasks left: " + todos.unfinishedTodoCount)
})
为什么每次 unfinishedTodoCount
发生变化时都会打印一条新消息?答案是这条经验法则
MobX 会对在跟踪函数执行期间读取的任何现有可观察属性做出反应。
要了解有关 MobX 如何确定需要对哪些可观察属性做出反应的更多信息,请查看 了解响应性 部分。
原则
MobX 使用单向数据流,其中操作更改状态,而状态反过来更新所有受影响的视图。
所有派生在状态发生变化时会自动且原子地更新。因此,永远无法观察到中间值。
默认情况下,所有派生都以同步方式更新。这意味着,例如,操作可以在更改状态后安全地直接检查计算值。
计算值以延迟方式更新。任何未被积极使用的计算值都不会被更新,直到它被用于副作用(I/O)为止。如果视图不再使用,它将被自动垃圾回收。
所有计算值都应该是纯净的。它们不应该更改状态。
要了解有关背景上下文的更多信息,请查看 MobX 背后的基本原理。
试一试!
您可以在 CodeSandbox 上亲自尝试以上示例。
代码风格检查
如果您发现难以采用 MobX 的思维模型,请将其配置为非常严格,并在您偏离这些模式时在运行时向您发出警告。查看 代码风格检查 MobX 部分。