React状态管理Mobx

1 https://zh.mobx.js.org/README.html

2 https://juejin.cn/post/7046710251382374413

3 https://cn.mobx.js.org/refguide/observable.html

​​mobx入门基础教程-慕课网​​

​​Mobx学习 - 掘金​​

十分钟入门 MobX & React

​​十分钟入门 MobX & React​​

概念

MobX区分了应用程序中的以下三个概念:

  1. State(状态)
  2. Actions(动作)
  3. Derivations(派生)

让我们仔细看看下面的这些概念,或者在​​10分钟的MobX和React简介​​中,您可以通过交互方式逐步深入了解这些概念,并构建一个简单的待办事项列表(Todo List)应用程序。

1 定义 State 并使其可观察

State(状态) 是驱动你的应用程序的数据。

通常来说,状态有领域特定状态(比如 Todo List 中的列表项数据)和视图状态 (比如当前选中的列表元素)。State 就像保存着数据的电子表格单元格一样。

将 State 存储在任何您喜欢的数据结构中:普通对象、数组、类、循环数据结构或引用。这与MobX的工作方式无关。

只要确保所有你想随时间改变的属性都被标记为​​observable​​,这样MobX就可以跟踪它们。

以下是一个简单的示例:

import { makeObservable, observable, action } from "mobx"class Todo {id = Math.random()title = ""finished = falseconstructor(title) {makeObservable(this, {title: observable,finished: observable,toggle: action})this.title = title}toggle() {this.finished = !this.finished}
}

提示: 在这个例子中我们可以用 ​​makeAutoObservable​​ 对其进行简化,但是为了能更详细的展示不同的概念,我们对其进行显式设置。

使用 ​​observable​​ 就像将对象的属性放在Excel表格的单元格中。但是和单元格不同的是,他们的值不仅仅是数值,也可以是引用、对象和数组。

接下来我们看一下被我们标记为 ​​action​​​ 的​​toggle​

2 使用 Action 更新 State

Action(动作) 是任意可以改变 State(状态) 的代码,比如用户事件处理、后端推送数据处理、调度器事件处理等等。

Action 就像用户在Excel单元格中输入了新的值。

在 ​​Todo​​​ 类中,我们可以看到 ​​toggle​​​ 方法改变了 ​​finished​​​ 属性的值,而 ​​finished​​​ 是被标记为 ​​observable​​​ 的。建议您将所有修改 ​​observable​​​ 值的代码标记为 ​​action​​。MobX 可以自动进行事务处理以轻松实现最佳性能。

使用 Action 可以帮助您更好地组织代码,并防止您在无意中修改 State。

在 MobX 术语中,可以修改 State 的方法被称为 action(动作) 。这与基于当前状态来生成新信息的 view(视图) 是不同的。 您代码中的每一个方法只应完成上述两个目标中的一个。

3 创建 Derivations 以便自动对 State 变化进行响应

任何 来源是State(状态) 并且不需要进一步交互的东西都是 Derivation(派生)。

Derivations 包括许多方式:

  • 用户界面
  • 派生数据 , 比如剩余未完成​​todos​​的数量
  • 后端集成 , 比如发送改变到服务器端

Mobx 区分了两种 Derivation :

  • Computed values,总是可以通过纯函数从当前的可观测 State 中派生。
  • Reactions, 当 State 改变时需要自动运行的副作用 (命令式编程和响应式编程之间的桥梁)

当最开始使用MobX时,人们容易过度使用 Reaction

黄金法则是,如果要基于当前 State 创建值,请始终使用 computed

3.1. 通过 computed 对派生值进行建模

你可以通过定义 getter 方法并使用 ​​makeObservable​​​ 将其标记为 ​​computed​​ 的方式创建一个 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​​​ 会在todos数组发生变化中或者 todos中的一个对象中的 ​​finished​​属性被修改时自动更新。

这些计算类似于 Excel 单元格中的公式。它们仅在需要时自动更新。也就是说,如果有观察者使用其结果时才会更新。也就是说,如果有有人关心其结果时才会更新。

3.2. 使用 reaction 对副作用建模

作为用户,要想在屏幕上看到状态或计算值的变化,就需要一个重新绘制部分GUI的 reactions

Reaction 和 computed 类似,但并不产生信息,而是产生副作用,如打印到控制台、发出网络请求、增量更新 React 组件树以便更新DOM等。

简而言之,reaction 是 ​​响应式编程​​​和​​指令式编程​​之间的桥梁。

到目前为止,最常用的 reaction 形式是UI组件。 注意,action 和 reaction 都可能引起副作用。 副作用应有一个清晰的、显式的起源,例如在提交表单时发出网络请求,应该从相关的事件处理程序显式触发。

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​​事件处理器调用toggle Action 后,会使对应的TodoView组件重绘,但仅当未完成任务的数量发生更改时才会使 TodoListView 组件重绘。

如果移除了​​Tasks left​​​这行代码(或者把他拆分到另一个组件中),​​TodoListView​​​组件就不再 ​​toggle​​ 执行时产生重绘了。

您可以查阅​​与 React 集成​​来了解更多有关 React 是如何与 MobX 协同运作的。

3.4. 自定义 Reaction

通常情况下你不需要使用它们,可以使用 ​​autorun​​​ ,​​reaction​​​ 或 ​​when​​ 方法来订制你的特殊业务场景。

比如,下面的 ​​autorun​​​ 将在​​unfinishedTodoCount​​的数量发生变化时输出日志。

// 一个自动观察state的函数
autorun(() => {console.log("Tasks left: " + todos.unfinishedTodoCount)
})

为什么每次 ​​unfinishedTodoCount​​发生改变时都会输出日志信息呢?答案是以下法则:

MobX对在执行跟踪函数期间读取的任何现有可观察属性作出响应

要了解更多关于MobX如何确定需要对哪些可观察对象作出响应的信息,请查看 ​​理解响应性​​ 章节。

原则

Mobx 使用单向数据流,利用 action 改变 state ,进而更新所有受影响的 view

  1. 所有的 derivations 将在 state 改变时自动且原子化地更新。因此不可能观察中间值。
  2. 所有的 derivations 默认将会同步更新,这意味着 action 可以在 state 改变 之后安全的直接获得 computed 值。
  3. computed value 的更新是惰性的,任何 computed value 在需要他们的副作用发生之前都是不激活的。
  4. 所有的 computed value 都应是纯函数,他们不应该修改 state

想了解更多背景,请查阅 ​​MobX背后的基本原则​​

创建可观察状态

属性,完整的对象,数组,Maps 和 Sets 都可以被转化为可观察对象。 使得对象可观察的基本方法是使用 ​​makeObservable​​ 为每个属性指定一个注解。 最重要的注解如下:

  • ​observable​​ 定义一个存储 state 的可追踪字段。
  • ​action​​ 将一个方法标记为可以修改 state 的 action。
  • ​computed​​ 标记一个可以由 state 派生出新的值并且缓存其输出的 getter。

像数组,Maps 和 Sets 这样的集合都将被自动转化为可观察对象。

makeObservable

用法:

  • ​makeObservable(target, annotations?, options?)​

这个函数可以捕获已经存在的对象属性并且使得它们可观察。任何 JavaScript 对象(包括类的实例)都可以作为 ​​target​​​ 被传递给这个函数。 一般情况下,​​makeObservable​​​ 是在类的构造函数中调用的,并且它的第一个参数是 ​​this​​​ 。 ​​annotations​​​ 参数将会为每一个成员映射 ​​注解​​​。需要注意的是,当使用 ​​装饰器​​​ 时,​​annotations​​ 参数将会被忽略。

派生数据并且接受参数的方法(例如:​​findUsersOlderThan(age: number): User[]​​​)不需要任何注解。 当我们从一个 reaction 中调用它们时,它们的读取操作仍然会被跟踪,但是为了避免内存泄漏,它们的输出将不会被记忆化。更详细的信息可以查看 ​​MobX-utils computedFn {🚀}​​。

Mobx 通过 ​​override​​​ 注解 ​​支持子类的使用,但会有一些局限性​​。

import { makeObservable, observable, computed, action, flow } from "mobx"class Doubler {valueconstructor(value) {makeObservable(this, {value: observable,double: computed,increment: action,fetch: flow})this.value = value}get double() {return this.value * 2}increment() {this.value++}*fetch() {const response = yield fetch("/api/value")this.value = response.json()}
}
所有带注解 的字段都是 不可配置的。
所有的不可观察(无状态)的字段(action, flow)都是 不可写的。

makeAutoObservable

使用:

  • ​makeAutoObservable(target, overrides?, options?)​

​makeAutoObservable​​​ 就像是加强版的 ​​makeObservable​​​,在默认情况下它将推断所有的属性。你仍然可以使用 ​​overrides​​​ 重写某些注解的默认行为。 具体来说,​​false​​​ 可用于从自动处理中排除一个属性或方法。 查看上面的代码分页获取示例。 与使用 ​​makeObservable​​​ 相比,​​makeAutoObservable​​​ 函数更紧凑,也更容易维护,因为新成员不需要显式地提及。 然而,​​makeAutoObservable​​​ 不能被用于带有 super 的类或 ​​子类​​。

推断规则:

  • 所有 自有 属性都成为 ​​observable​​。
  • 所有 ​​get​​​ters 都成为 ​​computed​​。
  • 所有 ​​set​​​ters 都成为 ​​action​​。
  • 所有 prototype 中的 functions 都成为 ​​autoAction​​。
  • 所有 prototype 中的 generator functions 都成为 ​​flow​​​。(需要注意,generators 函数在某些编译器配置中无法被检测到,如果 flow 没有正常运行,请务必明确地指定 ​​flow​​ 注解。)
  • 在 ​​overrides​​​ 参数中标记为 ​​false​​ 的成员将不会被添加注解。例如,将其用于像标识符这样的只读字段。
import { makeAutoObservable } from "mobx"function createDoubler(value) {return makeAutoObservable({value,get double() {return this.value * 2},increment() {this.value++}})
}
注意,类也可以跟 makeAutoObservable 合用。 示例中的差异就展示了将 MobX 应用于不同编程风格的方法。

observable

用法:

  • ​observable(source, overrides?, options?)​

​observable​​​ 注解可以作为一个函数进行调用,从而一次性将整个对象变成可观察的。 ​​source​​​ 对象将会被克隆并且所有的成员都将会成为可观察的,类似于 ​​makeAutoObservable​​​ 做的那样。 同样,你可以传入一个 ​​overrides​​ 对象来为特定的成员提供特定的注解。 查看上面的代码获取示例。

由 ​​observable​​​ 返回的对象将会使用 Proxy 包装,这意味着之后被添加到这个对象中的属性也将被侦测并使其转化为可观察对象(除非禁用 ​​proxy​​)。

​observable​​​ 方法也可以被像 ​​arrays​​​,​​Maps​​​ 和 ​​Sets​​ 这样的集合调用。这些集合也将被克隆并转化为可观察对象。

import { observable } from "mobx"const todosById = observable({"TODO-123": {title: "find a decent task management system",done: false}
})todosById["TODO-456"] = {title: "close all tickets older than two weeks",done: true
}const tags = observable(["high prio", "medium prio", "low prio"])
tags.push("prio: for fun")
与第一个例子中的 makeObservable 不同,observable 支持为对象添加(和删除)字段。 这使得 observable 非常适合用于像动态键控的对象、数组、Maps 和 Sets 之类的集合。

例子: 可观察数组

下面的例子创建了一个可观察对象并且使用 ​​autorun​​ 观察它。 使用 Map 和 Set 集合时,用法和这里类似。

import { observable, autorun } from "mobx"const todos = observable([{ title: "Spoil tea", completed: true },{ title: "Make coffee", completed: false }
])autorun(() => {console.log("Remaining:",todos.filter(todo => !todo.completed).map(todo => todo.title).join(", "))
})
// 打印: 'Remaining: Make coffee'todos[0].completed = false// 打印: 'Remaining: Spoil tea, Make coffee'todos[2] = { title: "Take a nap", completed: false }
// 打印: 'Remaining: Spoil tea, Make coffee, Take a nap'todos.shift()
// 打印: 'Remaining: Make coffee, Take a nap'

可观察的数组还有一些特别好用的实用函数:

  • ​clear()​​ 从数组中清除所有元素。
  • ​replace(newItems)​​ 将数组中现有的元素全部替换成 newItems。
  • ​remove(value)​​​ 根据 value 从数组中删除一个元素。如果找到并删除了元素,返回 ​​true​​。

可用的注解

注解

描述

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​​ 获取更多信息。不可写。

action.bound

类似于 action,但是会将 action 绑定到实例,因此将始终设置 this。不可写。

computed

可以用在 ​​getter​​​ 上,用来将其声明为可缓存的派生值。查看 ​​computeds​​ 获取更多信息。

computed.struct

类似于 computed,但如果重新计算后的结果在结构上与之前的结果相等,那么观察者将不会收到通知。

TRUE

推断最佳注解。查看 ​​makeAutoObservable​​ 获取更多信息。

FALSE

刻意不为该属性指定注解。

flow

创建一个 flow 管理异步进程。查看 ​​flow​​ 获取更多信息。需要注意的是,推断出来的 TypeScript 返回类型可能会出错。 不可写。

flow.bound

类似于 flow, 但是会将 flow 绑定到实例,因此将始终设置 this。 不可写。

override

​​用于子类覆盖继承的 action,flow,computed,action.bound​​。

autoAction

不应被显式调用,但 makeAutoObservable 内部会对其进行调用,以便根据调用上下文将方法标识为 action 或者派生值。

使用 actions 更新 state

用法:

  • ​action​(注解)
  • ​action(fn)​
  • ​action(name, fn)​

所有的应用程序都有 actions。action 就是任意一段修改 state 的代码。原则上,actions 总会为了对一个事件做出响应而发生。例如,点击了一个按钮,一些输入被改变了,一个 websocket 消息被送达了,等等。

尽管 ​​makeAutoObservable​​ 可以自动帮你声明一部分 actions,但是 MobX 还是要求你声明你的 actions。Actions 可以帮助你更好的组织你的代码并提供以下性能优势:

  1. 它们在 ​​transactions​​ 内部运行。任何可观察对象在最外层的 action 完成之前都不会被更新,这一点保证了在 action 完成之前,action 执行期间生成的中间值或不完整的值对应用程序的其余部分都是不可见的。
  2. 默认情况下,不允许在 actions 之外改变 state。这有助于在代码中清楚地对状态更新发生的位置进行定位。

​action​​ 注解应该仅用于会修改 state 的函数。派生其他信息(执行查询或者过滤数据)的函数不应该被标记为 actions,以便 MobX 可以对它们的调用进行跟踪。 带有 ​​action​​ 注解的成员是不可枚举的。

例子

makeObservable

import { makeObservable, observable, action } from "mobx"class Doubler {value = 0constructor(value) {makeObservable(this, {value: observable,increment: action})}increment() {// 观察者不会看到中间状态.this.value++this.value++}
}

使用 action 包装函数

为了尽可能地利用 MobX 的事务性,actions 应该尽可能被传到外围。如果一个类方法会修改 state,可以将其标记为 action。把事件处理函数标记为 actions 就更好了,因为最外层的事务起着决定性作用。一个未被标记的、会接着调用两个 actions 的事件处理函数仍然将会生成两个事务。

为了帮助创建基于 action 的事件处理函数,​​action​​​ 不仅仅是一个注解,更是一个高阶函数。可以使用函数将它作为一个参数来调用,在这种情况下它将会返回一个有着相同签名的使用 ​​action​​ 包装过的函数。

例如在 React 中,可以按照下面的方式包装 ​​onClick​​ 事件处理函数。

const ResetButton = ({ formState }) => (<buttononClick={action(e => {formState.resetPendingUploads()formState.resetValues()e.stopPropagation()})}>Reset form</button>
)

为了更好的调试体验,我们推荐为被包装的函数命名,或者将名称作为 ​​action​​ 的第一个参数进行传递。

Actions 和继承

只有定义在原型上的函数可以被子类覆盖:

class Parent {// on instancearrowAction = () => {}// on prototypeaction() {}boundAction() {}constructor() {makeObservable(this, {arrowAction: actionaction: action,boundAction: action.bound,})}
}
class Child extends Parent {// THROWS: TypeError: Cannot redefine property: arrowActionarrowAction = () => {}// OKaction() {}boundAction() {}constructor() {super()makeObservable(this, {arrowAction: override,action: override,boundAction: override,})}
}

想要将单个的 action 绑定 到 ​​this​​​,可以使用 ​​action.bound​​​ 代替箭头函数。 查看 ​​​subclassing​​ 获取更多信息。

异步 actions

从本质上讲,异步进程在 MobX 中不需要任何特殊处理,因为不论是何时引发的所有 reactions 都将会自动更新。 而且因为可观察对象是可变的,因此在 action 执行过程中保持对它们的引用一般是安全的。 然而,在异步进程中更新可观察对象的每个步骤(tick)都应该被标识为 ​​action​​。 我们可以通过利用上述的 API 以多种方式实现这一点,如下所示。

例如,在处理 Promise 时,更新 state 的处理程序应该被 ​​action​​ 包装起来,或者被标记为 actions,如下所示。

Actions 和继承
只有定义在原型上的函数可以被子类覆盖:
class Parent {// on instancearrowAction = () => {}// on prototypeaction() {}boundAction() {}constructor() {makeObservable(this, {arrowAction: actionaction: action,boundAction: action.bound,})}
}
class Child extends Parent {// THROWS: TypeError: Cannot redefine property: arrowActionarrowAction = () => {}// OKaction() {}boundAction() {}constructor() {super()makeObservable(this, {arrowAction: override,action: override,boundAction: override,})}
}
想要将单个的 action 绑定 到 this,可以使用 action.bound 代替箭头函数。
查看 subclassing 获取更多信息。
异步 actions
从本质上讲,异步进程在 MobX 中不需要任何特殊处理,因为不论是何时引发的所有 reactions 都将会自动更新。 而且因为可观察对象是可变的,因此在 action 执行过程中保持对它们的引用一般是安全的。 然而,在异步进程中更新可观察对象的每个步骤(tick)都应该被标识为 action。 我们可以通过利用上述的 API 以多种方式实现这一点,如下所示。
例如,在处理 Promise 时,更新 state 的处理程序应该被 action 包装起来,或者被标记为 actions,如下所示。

通过 computeds 派生信息

使用:

  • ​computed​(注解)
  • ​computed(options)​(注解)
  • ​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 = 0amount = 1constructor(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: 0console.log(order.total)
// (不会重新计算!)// 0order.amount = 5// Computing...// (无需 autorun)order.price = 2// Computing...// Total: 10stop()order.price = 3// 计算值和 autorun 都不会被重新计算.

上面的例子很好地展示了 ​​计算值​​​ 的好处,它充当了缓存点的角色。 即使我们改变了 ​​amount​​​,进而触发了 ​​total​​​ 的重新计算, 也不会触发 ​​autorun​​​,因为 ​​total​​​ 将会检测到其输出未发生任何改变,所以也不需要更新 ​​autorun​​。

相比之下,如果 ​​total​​​ 没有被注解,那么 ​​autorun​​​ 会把副作用运行 3 次, 因为它将直接依赖于 ​​total​​​ 和 ​​amount​​​。​​自己试一下吧​​。

上图是为以上示例创建的依赖图。

规则

使用计算值时,请遵循下面的最佳实践:

  1. 它们不应该有副作用或者更新其他可观察对象。
  2. 避免创建和返回新的可观察对象。
  3. 它们不应该依赖非可观察对象的值

使用 reactions 处理副作用 {🚀}

reactions 是需要理解的重要概念,因为他可以将 MobX 中所有的特性有机地融合在一起。 reactions 的目的是对自动发生的副作用进行建模。 它们的意义在于为你的可观察状态创建消费者,以及每当关联的值发生变化时,自动运行副作用。

然而,理解了这一点之后,重要的是要认识到这里所讨论的 API 应该很少会被用到。 它们经常被抽象到其他的库里面(例如,mobx-react)或者你的应用程序中其他特定的抽象库。

但是,为了理解 MobX,让我们看一下如何创建 reactions。 最简单的方式是使用 ​​autorun​​​ 工具函数。 除此之外,还有 ​​reaction​​​ 和 ​​when​​。

Autorun

用法:

  • ​autorun(effect: (reaction) => void)​

​autorun​​​ 函数接受一个函数作为参数,每当该函数所观察的值发生变化时,它都应该运行。 当你自己创建 ​​autorun​​​ 时,它也会运行一次。它仅仅对可观察状态的变化做出响应,比如那些你用 ​​observable​​​ 或者 ​​computed​​ 注释的。

tracking 如何工作

Autorun 通过在响应式上下文运行 ​​effect​​ 来工作。在给定的函数执行期间,MobX 会持续跟踪被 effect 直接或间接读取过的所有可观察对象和计算值。 一旦函数执行完毕,MobX 将收集并订阅所有被读取过的可观察对象,并等待其中任意一个再次发生改变。 一旦有改变发生,autorun 将会再次触发,重复整个过程。

这就是下面的示例的工作方式。

例子

import { makeAutoObservable, autorun } from "mobx"class Animal {nameenergyLevelconstructor(name) {this.name = namethis.energyLevel = 100makeAutoObservable(this)}reduceEnergy() {this.energyLevel -= 10}get isHungry() {return this.energyLevel < 50}
}const giraffe = new Animal("Gary")autorun(() => {console.log("Energy level:", giraffe.energyLevel)
})autorun(() => {if (giraffe.isHungry) {console.log("Now I'm hungry!")} else {console.log("I'm not hungry!")}
})console.log("Now let's change state!")
for (let i = 0; i < 10; i++) {giraffe.reduceEnergy()
}

运行上面的代码,你将会看到下面的输出:

正如你在上面输出的前两行看到的,两个 autorun 函数在初始化时都会运行一次。这就是在运行 ​​for​​ 循环前可以看到的内容。

一旦我们运行 ​​for​​​ 循环使用 ​​reduceEnergy​​​ action 改变 ​​energyLevel​​​, 每当 ​​autorun​​ 观察到可观察状态的变化时, 我们将会看到一条新的 log 条目被打印出来:

  1. 对于“Energy level”函数,它总是可以检测到 ​​energyLevel​​ 可观察对象的变化,总共发生 10 次。
  2. 对于“Now I'm hungry”函数,它总是可以检测到 ​​isHungry​​ 计算值的变化, 总共发生 1 次。

Reaction

用法:

  • ​reaction(() => value, (value, previousValue, reaction) => { sideEffect }, options?)​​.

​reaction​​​ 类似于 ​​autorun​​,但可以让你更加精细地控制要跟踪的可观察对象。 它接受两个函数作为参数:第一个,data 函数,其是被跟踪的函数并且其返回值将会作为第二个函数,effect 函数,的输入。 重要的是要注意,副作用只会对 data 函数中被访问过的数据做出反应,这些数据可能少于 effect 函数中实际使用的数据。

一般的模式是在 data 函数中返回你在副作用中需要的所有数据, 并以这种方式更精确地控制副作用触发的时机。 与 ​​autorun​​ 不同,副作用在初始化时不会自动运行,而只会在 data 表达式首次返回新值之后运行。

例子: 数据和副作用函数

When

使用:

  • ​when(predicate: () => boolean, effect?: () => void, options?)​
  • ​when(predicate: () => boolean, options?): Promise​

​when​​ 会观察并运行给定的 predicate 函数,直到其返回 ​​true​​。 一旦 predicate 返回了 true,给定的 effect 函数就会执行并且自动执行器函数将会被清理掉。

如果你没有传入 ​​effect​​​ 函数,​​when​​​ 函数返回一个 ​​Promise​​ 类型的 disposer,并允许你手动取消。

例子:以一种响应式的方式将值清理掉

​await when(...)​

如果你没有提供 ​​effect​​​ 函数,​​when​​​ 将会返回一个 ​​Promise​​​。这样会跟 ​​async / await​​ 很好地结合在一起,让你可以等待可观察对象中的变化。

async function() {await when(() => that.isVisible)// etc...
}

如果要提前取消 ​​when​​​,可以对它返回的 Promise 调用 ​​.cancel()​​ 函数。

规则

这里是一些 reactive context 需要遵守的规则:

  1. 默认情况下,如果可观察对象发生了改变,受其影响的 reactions 会立即(同步)运行。然而,它们直到当前最外层的 (trans)action 执行结束后才会运行。
  2. autorun 只会跟踪给定函数在同步执行过程中所读取的可观察对象,不会跟踪异步发生的变化。
  3. autorun 不会跟踪被其调用的 action 所读取的可观察对象,因为 action 始终不会被追踪

有关 MobX 会与不会对各种值作出响应的更多示例,请查看 ​​理解响应性​​​ 部分。 对于依赖跟踪如何工作的更详细的技术细节,请阅读博客 ​​Becoming fully reactive: an in-depth explanation of MobX​​。

Always dispose of reactions

传递给 ​​autorun​​​,​​reaction​​​ 和 ​​when​​ 的函数只有在它们观察的所有对象都被 GC 之后才会被 GC。原则上,它们一直等待可观察对象发生新的变化。 为了阻止 reactions 永远地等待下去,它们总是会返回一个 disposer 函数,该函数可以用来停止执行并且取消订阅所使用的任何可观察对象。

const counter = observable({ count: 0 })// 初始化一个 autorun 并且打印 0.const disposer = autorun(() => {console.log(counter.count)
})// 打印: 1
counter.count++// 停止 autorun.
disposer()// 不会打印消息.
counter.count++

我们强烈建议你,一旦不再需要这些方法中的副作用时,请务必调用它们所返回的 disposer 函数。 否则可能导致内存泄漏。

​reaction​​​ 和 ​​autorun​​​ 中 effect 函数的第二个参数 ​​reaction​​​ 也可以被用来提前把 reaction 清理掉(通过调用 ​​reaction.dispose()​​)。

例子: 内存泄漏

谨慎地使用 reactions!

就像上面已经说过的那样,你不会经常创建 reactions。 很有可能你的应用程序不会直接使用这些 API 中的任何一个,而只会通过比如使用 mobx-react 绑定中的 ​​observer​​ 这样间接的方式创建出 reaction。

在你创建 reaction 之前,最好检查一下它是否符合下面几条原则:

  1. 只有在引起副作用的一方与副作用之间没有直接关系的情况下才使用 reaction: 如果一个副作用会为了响应很小的一组 events 或 actions 而执行,那么直接从那些特定的 action 中触发这个副作用通常会更容易理解。例如,如果按下表单提交按钮会导致一个 POST 网络请求的发送,那么为了响应 ​​onclick​​​ 事件,直接触发这个副作用就会比通过 reaction 间接触发更容易理解。相比之下,如果你对表单状态的一切修改最后都会被自动存储到 localStorage,那么使用一个 reaction 可能就会很有帮助,这样你就不用在每个独立的 ​​onChange​​ 事件中触发这个副作用了。
  2. reactions 不应该更新其他可观察对象:这个 reaction 是否会修改其他可观察对象?如果答案是肯定的,那么你一般应该把你想要更新的可观察对象注解为 ​​computed​​​ 值。例如,如果一个待办事项的集合 ​​todos​​​ 发生了变化,那么请不要使用 reaction 来计算剩余待办 ​​remainingTodos​​​ 的数量,而要把 ​​remainingTodos​​ 注解为计算值。这将使得代码更容易理解和调试。reaction 不应该计算生成新的数据,而只应该触发副作用。
  3. reactions 应该是独立的:你的代码是否依赖其他必须首先运行的 reaction?如果发生这种情况,你可能违反了第一条规则, 你可以选择将你需要创建的新 reaction 合并到它所依赖 reaction 中。MobX 并不能保证 reaction 的执行顺序。

有些实践并不符合上述原则。这就是为什么它们是原则,而不是法则。 但是,例外情况很少见,只有在万不得已的情况下才违反它们。

git clone -b dev-from-tag-3.3.0 http://10.8.59.16/Code/kubesphere/console.git

​​http://10.8.59.16/Code/kubesphere/console.git​​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/757532.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

警惕!On Hold被踢,2本1区,5本Springer旗下,共8本SCI/SSCI被剔除!

毕业推荐 SSCI&#xff08;ABS一星&#xff09; • 社科类&#xff0c;3.0-4.0&#xff0c;JCR2区&#xff0c;中科院3区 • 13天录用&#xff0c;28天见刊&#xff0c;13天检索 SCIE&#xff1a; • 计算机类&#xff0c;6.5-7.0&#xff0c;JCR1区&#xff0c;中科院2区…

农业气象站在农业生产中的应用—气象科普

农业气象站在农业生产中发挥着至关重要的作用。它能够有效监测和记录农田环境中的各类气象要素&#xff0c;为农民提供科学、准确的气象数据&#xff0c;帮助他们更好地掌握天气变化规律&#xff0c;从而合理安排农业生产活动。 首先&#xff0c;农业气象站能够实时提供温度、…

使用 Clojure 进行 OpenCV 开发简介

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;如何将OpenCV Java 与Eclipse结合使用 下一篇&#xff1a; OpenCV4.9.0在Android 开发简介 ​警告 本教程可以包含过时的信息。 从 OpenCV 2.4.4 开始&#xff0c;OpenCV 支持…

挑战设计极限!电路仿真软件成功案例大揭秘,助您圆梦创新之路

在电子设计领域&#xff0c;电路仿真软件扮演着至关重要的角色。它们不仅能够帮助工程师们模拟和分析电路的性能&#xff0c;还能够加速设计过程&#xff0c;降低成本&#xff0c;提高产品的质量和可靠性。今天&#xff0c;让我们一起挑战设计极限&#xff0c;揭秘电路仿真软件…

Java基础---反射

什么是反射&#xff1f; 反射允许对成员变量&#xff0c;成员方法和构造方法的信息进行编程访问。 这么说可能比较抽象&#xff0c;可以简单理解为&#xff1a;反射就是一个人&#xff0c;可以把类里面的成员变量&#xff0c;成员方法&#xff0c;构造方法都获取出来。 并且可…

Springcloud智慧工地APP云综合平台源码 SaaS服务

目录 智慧工地功能介绍 一、项目人员 二、视频监控 三、危大工程 四、绿色施工 五、安全隐患 具体功能介绍&#xff1a; 1.劳务管理&#xff1a; 2.施工安全管理&#xff1a; 3.视频监控管理&#xff1a; 4.机械安全管理&#xff1a; 5.危大工程监管&#xff1a; …

ctf_show笔记篇(web入门---反序列化)

目录 反序列化 254&#xff1a;无用&#xff0c;是让熟悉序列化这个东西的 255&#xff1a;直接使$isViptrue 256&#xff1a;还是使用变量覆盖 257&#xff1a;开始使用魔法函数 258&#xff1a;将序列化最前面的过滤了&#xff0c;使用绕过 259: 这一题需要看writeup才…

windows10 WSL启动Ubuntu虚拟机,安装DolphinScheduler

文章目录 1. 启动WSL与虚拟机2. 安装Docker与DolphinScheduler容器 1. 启动WSL与虚拟机 使用管理员权限运行命令&#xff1a; Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux重启后即可创建虚拟机 在Microsoft Store中搜索Ubuntu&…

研二双9找个日常实习都找不到,哎!

投了几家日常&#xff0c;要不就面完没反应&#xff0c;要不就秒挂&#xff0c;看不透了。是最近都在忙着处理春招和暑期实习吗&#xff0c;怎么连个日常实习都找不到&#xff1f; 个人背景双9&#xff0c;lc以前刷过200道&#xff0c;最近没怎么碰过。 腾讯 3.13 一面&#xf…

给老婆整了个短剧搜索机器人APP

最近短剧挺火&#xff0c;很多群友们都在做一些资源分享&#xff0c;老胡于是基于这些资源做了个短剧搜索引擎&#xff0c;挺多朋友喜欢看的&#xff0c;我老婆也在看哈哈&#xff0c;真上头&#xff0c;废话不多说&#xff0c;上短剧机器人。 短剧机器人 直接在微信群输入&…

麒麟 V10 一键安装 Oracle 11GR2(231017)单机版

Oracle 一键安装脚本&#xff0c;演示 麒麟 V10 一键安装 Oracle 11GR2 单机版过程&#xff08;全程无需人工干预&#xff09;&#xff1a;&#xff08;脚本包括 ORALCE PSU/OJVM 等补丁自动安装&#xff09; ⭐️ 脚本下载地址&#xff1a;Shell脚本安装Oracle数据库 脚本第…

提升合规性!Zoho如何优化CRM产品合规性?

在企业数字化和信息化高速发展的今天&#xff0c;CRM管理系统成为越来越多企业的选择。然而&#xff0c;不是所有CRM供应商都有合规意识。合规性不应当只是一项法律规定&#xff0c;更是保证CRM供应商持续发展、赢得客户信赖以及应付监管压力的关键支撑。Zoho对企业合规性的重视…

Python探索反距离加权空间插值方法的深度

介绍 反距离加权 (IDW) 是一种广泛用于地理信息系统 (GIS) 和环境科学的空间插值技术,用于根据附近位置的值估计任何位置的缺失值。其基本原理很直观:距离兴趣点较近的位置比距离较远的位置更相似。本文深入探讨了 IDW 的方法、应用、优势和局限性,深入探讨了其在空间分析中…

招聘必备知识:求职者跟进邮件如何写?

写一封正确的招聘跟进邮件是一门艺术。在面试结束后给你的候选人发一封好的后续邮件可以为招聘工作创造奇迹&#xff0c;甚至最终入职的候选人也是如此。 与候选人保持良好的沟通&#xff0c;可以确保他们一直参与其中&#xff0c;给予他们应得的尊重和赞赏。然而&#xff0c;…

机器学习——终身学习

终身学习 AI不断学习新的任务&#xff0c;最终进化成天网控制人类终身学习&#xff08;LLL&#xff09;&#xff0c;持续学习&#xff0c;永不停止的学习&#xff0c;增量学习 用线上收集的资料不断的训练模型 问题就是对之前的任务进行遗忘&#xff0c;在之前的任务上表现不好…

HarmonyOS系统开发ArkTS常用组件按钮及参数

Button组件有两种使用方式&#xff0c;分别是不包含子组件和包含子组件两种方式。不同方式Button 组件所需的参数有所不同。 1、不包含子组件 Button(label?: string, options?: { type?: ButtonType, stateEffect?: boolean }) label为按钮上显示的文字内容options.type…

21个 JVM 技术点详解(附面试解答)

最近兄弟们面试&#xff0c;都逃不过被 JVM 问题轰炸的命运&#xff0c;为啥面试官喜欢拿 JVM 说事呢&#xff1f;V 哥认为&#xff0c;除了要问倒你&#xff0c;就是要压你薪水&#xff0c;咱绝对不能怂&#xff0c;俗话说的好&#xff1a;兵来将挡&#xff0c;水来土掩&#…

模拟面试

1.TCP通信中的三次握手和四次挥手过程 三次握手 1.客户端像向服务器端发送连接请求 2.服务器应答连接请求 3.客户端与服务器简历连接 四次挥手&#xff1a; 客户端或服务器端发起断开请求,这里假设客户端发送断开请求 1.客户端向服务器发送断开请求 2.服务器应答断开请求 3.服…

JavaSE(上)-Day6

JavaSE&#xff08;上&#xff09;-Day6 数组数组的定义数组的初始化打印数组分析数组索引数组内存图 方法方法的定义和调用方法的重载方法的内存图 二维数组二位数组的创建和初始化二维数组的内存图 数组 1.数组是一种容器&#xff0c;可以一次存储多个相同类型的数据 数组的…

opengl日记9-opengl使用纹理示例

环境 系统&#xff1a;ubuntu20.04opengl版本&#xff1a;4.6glfw版本&#xff1a;3.3glad版本&#xff1a;4.6cmake版本&#xff1a;3.16.3gcc版本&#xff1a;10.3.0 直接上代码 CMakeLists.txt cmake_minimum_required(VERSION 3.5) set(CMAKE_C_STANDARD 11) set(CMAKE_…