MobX 的要点
¥The gist of MobX
概念
¥Concepts
MobX 在你的应用中区分以下三个概念:
¥MobX distinguishes between the following three concepts in your application:
状态
¥State
操作
¥Actions
推导
¥Derivations
让我们在下面或 10 分钟介绍 MobX 和 React 中仔细研究这些概念,你可以在其中以交互方式逐步深入了解这些概念并构建一个简单的待办事项列表应用。
¥Let's take a closer look at these concepts below, or alternatively, in the 10 minute introduction to MobX and React, where you can interactively dive deeper into these concepts step by step and build a simple Todo list app.
有些人可能会在下面描述的概念中认识到 "信号" 的概念。这是正确的,MobX 是一个基于信号的状态管理库。
¥Some might recognise the concept of "Signals" in the concepts described below. This is correct, MobX is a signal based state management library avant la lettre.
1. 定义状态并使其可观察
¥ Define state and make it observable
状态是驱动应用的数据。通常,存在特定于域的状态(例如待办事项列表),并且存在视图状态(例如当前选定的元素)。状态就像保存值的电子表格单元格。
¥State is the data that drives your application. Usually, there is domain specific state like a list of todo items, and there is view state, such as the currently selected element. State is like spreadsheet cells that hold a value.
将状态存储在你喜欢的任何数据结构中:普通对象、数组、类、循环数据结构或引用。这对于 MobX 的工作并不重要。只需确保你想要随时间更改的所有属性都标记为 observable
,以便 MobX 可以跟踪它们。
¥Store state in any data structure you like: plain objects, arrays, classes, cyclic data structures or references. It doesn't matter for the workings of MobX.
Just make sure that all properties you want to change over time are marked as observable
so MobX can track them.
这是一个简单的例子:
¥Here is a simple example:
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
就像将对象的属性转换为电子表格单元格。但与电子表格不同的是,这些值不仅可以是原始值,还可以是引用、对象和数组。
¥Using observable
is like turning a property of an object into a spreadsheet cell.
But unlike spreadsheets, these values can not only be primitive values, but also references, objects and arrays.
Tip: Prefer classes, plain objects or decorators? MobX supports many styles.
这个例子可以使用 makeAutoObservable
来缩短,但是通过明确我们可以更详细地展示不同的概念。请注意,MobX 并不规定对象样式,也可以使用普通对象,也可以使用装饰器来实现更简洁的类。请参阅页面了解更多详情。
¥This example can be shortened using makeAutoObservable
, but by being explicit we can showcase the different concepts in greater detail.
Note that MobX doesn't dictate an object style, plain objects instead can be used as well, as can decorators for even more concise classes. See the page for more details.
但是我们标记为 action
的 toggle
呢?
¥But what about toggle
, which we marked as action
?
2. 使用操作更新状态
¥ Update state using actions
操作是改变状态的任何代码片段。用户事件、后端数据推送、计划事件等。操作就像用户在电子表格单元格中输入新值。
¥An action is any piece of code that changes the state. User events, backend data pushes, scheduled events, etc. An action is like a user that enters a new value into a spreadsheet cell.
在上面的 Todo
模型中,你可以看到我们有一个 toggle
方法来更改 finished
的值。finished
标记为 observable
。建议你将任何更改 observable
的代码标记为 action
。这样 MobX 就可以自动应用事务,轻松实现最佳性能。
¥In the Todo
model above you can see that we have a toggle
method that changes the value of finished
. finished
is marked as observable
. It is recommended that you mark any piece of code that changes observable
's as an action
. That way MobX can automatically apply transactions for effortless optimal performance.
使用操作可以帮助你构建代码并防止你无意中更改状态。修改状态的方法在 MobX 术语中称为操作。与视图相反,视图根据当前状态计算新信息。每种方法最多只能服务于这两个目标之一。
¥Using actions helps you structure your code and prevents you from inadvertently changing state when you don't intend to. Methods that modify state are called actions in MobX terminology. In contrast to views, which compute new information based on the current state. Every method should serve at most one of those two goals.
3. 创建自动响应状态变化的派生
¥ Create derivations that automatically respond to state changes
任何可以从状态派生而无需任何进一步交互的东西都是派生。派生有多种形式:
¥Anything that can be derived from the state without any further interaction is a derivation. Derivations exist in many forms:
用户界面
¥The user interface
导出数据,如剩余的
todos
的数量¥Derived data, such as the number of remaining
todos
后端集成,例如 将更改发送到服务器
¥Backend integrations, e.g. sending changes to the server
MobX 区分两种派生:
¥MobX distinguishes between two kinds of derivations:
计算值,始终可以使用纯函数从当前可观察状态导出
¥Computed values, which can always be derived from the current observable state using a pure function
反应,状态改变时需要自动发生的副作用(命令式和反应式编程之间的桥梁)
¥Reactions, side effects that need to happen automatically when the state changes (bridge between imperative and reactive programming)
当开始使用 MobX 时,人们往往会过度使用反应。黄金法则是,如果你想根据当前状态创建值,请始终使用 computed
。
¥When starting with MobX, people tend to overuse reactions.
The golden rule is, always use computed
if you want to create a value based on the current state.
3.1.使用计算得到的模型导出值
¥3.1. Model derived values using computed
要创建计算值,请使用 JS getter 函数 get
定义一个属性,并将其标记为 computed
和 makeObservable
。
¥To create a computed value, define a property using a JS getter function get
and mark it as computed
with makeObservable
.
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 将确保在添加待办事项或修改 finished
属性之一时自动更新 unfinishedTodoCount
。
¥MobX will ensure that unfinishedTodoCount
is updated automatically when a todo is added or when one of the finished
properties is modified.
这些计算类似于 MS Excel 等电子表格程序中的公式。它们会自动更新,但仅在需要时才会更新。也就是说,如果某些东西对他们的结果感兴趣。
¥These computations resemble formulas in spreadsheet programs like MS Excel. They update automatically, but only when required. That is, if something is interested in their outcome.
3.2.使用反应式模拟副作用
¥3.2. Model side effects using reactions
作为用户,为了能够在屏幕上看到状态或计算值的变化,需要重新绘制 GUI 的一部分的反应。
¥For you as a user to be able to see a change in state or computed values on the screen, a reaction that repaints a part of the GUI is needed.
反应类似于计算值,但它们不产生信息,而是产生副作用,例如打印到控制台、发出网络请求、增量更新 React 组件树以修补 DOM 等。
¥Reactions are similar to computed values, but instead of producing information, they produce side effects like printing to the console, making network requests, incrementally updating React component tree to patch the DOM, etc.
简而言之,反应连接了 reactive 和 imperative 编程的世界。
¥In short, reactions bridge the worlds of reactive and imperative programming.
到目前为止,最常用的反应形式是 UI 组件。请注意,动作和反应都可能引发副作用。具有明确、明确的触发来源的副作用(例如在提交表单时发出网络请求)应从相关事件处理程序显式触发。
¥By far the most used form of reactions are UI components. Note that it is possible to trigger side effects from both actions and reactions. Side effects that have a clear, explicit origin from which they can be triggered, such as making a network request when submitting a form, should be triggered explicitly from the relevant event handler.
3.3.反应式 React 组件
¥3.3. Reactive React components
如果你使用的是 React,则可以使用 安装时选择的 绑定包中的 observer
函数封装组件,从而使组件具有反应性。在此示例中,我们将使用更轻量级的 mobx-react-lite
包。
¥If you are using React, you can make your components reactive by wrapping them with the observer
function from the bindings package you've chosen during installation. In this example, we're going to use the more lightweight mobx-react-lite
package.
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 将简单地确保组件在需要时始终重新渲染,仅此而已。
¥observer
converts React components into derivations of the data they render.
When using MobX there are no smart or dumb components.
All components render smartly, but are defined in a dumb manner. MobX will simply make sure the components are always re-rendered whenever needed, and never more than that.
因此,上例中的 onClick
处理程序将强制正确的 TodoView
组件在使用 toggle
操作时重新渲染,但仅当未完成任务的数量发生变化时才会导致 TodoListView
组件重新渲染。如果你删除 Tasks left
行(或将其放入单独的组件中),则在勾选任务时 TodoListView
组件将不再重新渲染。
¥So the onClick
handler in the above example will force the proper TodoView
component to re-render as it uses the toggle
action, but will only cause the TodoListView
component to re-render if the number of unfinished tasks has changed.
And if you would remove the Tasks left
line (or put it into a separate component), the TodoListView
component would no longer re-render when ticking a task.
要了解有关 React 如何与 MobX 配合使用的更多信息,请查看 React 集成 部分。
¥To learn more about how React works with MobX, check out the React integration section.
3.4.定制反应式
¥3.4. Custom reactions
你很少需要它们,但可以使用 autorun
、reaction
或 when
函数来创建它们,以适应你的具体情况。例如,每次 unfinishedTodoCount
的数量发生变化时,下面的 autorun
都会打印一条日志消息:
¥You will need them rarely, but they can be created using the autorun
,
reaction
or when
functions to fit your specific situations.
For example, the following autorun
prints a log message every time the amount of unfinishedTodoCount
changes:
// A function that automatically observes the state.
autorun(() => {
console.log("Tasks left: " + todos.unfinishedTodoCount)
})
为什么每次更改 unfinishedTodoCount
时都会打印一条新消息?答案是这个经验法则:
¥Why does a new message get printed every time the unfinishedTodoCount
is changed? The answer is this rule of thumb:
MobX 对在跟踪函数执行期间读取的任何现有可观察属性做出反应。
¥MobX reacts to any existing observable property that is read during the execution of a tracked function.
要了解有关 MobX 如何确定需要对哪些可观察量做出反应的更多信息,请查看 了解反应性 部分。
¥To learn more about how MobX determines which observables need to be reacted to, check out the Understanding reactivity section.
原则
¥Principles
MobX 使用单向数据流,其中操作会更改状态,从而更新所有受影响的视图。
¥MobX uses a uni-directional data flow where actions change the state, which in turn updates all affected views.
当状态改变时,所有派生都会自动且原子地更新。因此,永远不可能观察到中间值。
¥All derivations are updated automatically and atomically when the state changes. As a result, it is never possible to observe intermediate values.
所有推导默认同步更新。这意味着,例如,操作可以在更改状态后直接安全地检查计算值。
¥All derivations are updated synchronously by default. This means that, for example, actions can safely inspect a computed value directly after altering the state.
计算值被延迟更新。任何未主动使用的计算值在需要产生副作用 (I/O) 之前都不会更新。如果视图不再使用,它将被自动垃圾收集。
¥Computed values are updated lazily. Any computed value that is not actively in use will not be updated until it is needed for a side effect (I/O). If a view is no longer in use it will be garbage collected automatically.
所有计算值都应该是纯粹的。他们不应该改变状态。
¥All computed values should be pure. They are not supposed to change state.
要了解有关背景上下文的更多信息,请查看 MobX 背后的基本原则。
¥To learn more about the background context, check out the fundamental principles behind MobX.
试试看!
¥Try it out!
你可以在 CodeSandbox 上自己玩一下上面的例子。
¥You can play with the above examples yourself on CodeSandbox.
代码检查
¥Linting
如果你发现很难采用 MobX 的心理模型,请将其配置得非常严格,并在运行时在你偏离这些模式时发出警告。查看 MobX 的 linting 部分。
¥If you find it hard to adopt the mental model of MobX, configure it to be very strict and warn you at runtime whenever you deviate from these patterns. Check out the linting MobX section.