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 {
    value

    constructor(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 = 0

    constructor(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 instance
    arrowAction = () => {}

    // on prototype
    action() {}
    boundAction() {}

    constructor() {
        makeObservable(this, {
            arrowAction: action
            action: action,
            boundAction: action.bound,
        })
    }
}
class Child extends Parent {
    // THROWS: TypeError: Cannot redefine property: arrowAction
    arrowAction = () => {}

    // OK
    action() {}
    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 instance
    arrowAction = () => {}

    // on prototype
    action() {}
    boundAction() {}

    constructor() {
        makeObservable(this, {
            arrowAction: action
            action: action,
            boundAction: action.bound,
        })
    }
}
class Child extends Parent {
    // THROWS: TypeError: Cannot redefine property: arrowAction
    arrowAction = () => {}

    // OK
    action() {}
    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 = 0
    amount = 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)
// (不会重新计算!)// 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​​​。​​自己试一下吧​​。

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

规则

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

  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 {
    name
    energyLevel

    constructor(name) {
        this.name = name
        this.energyLevel = 100
        makeAutoObservable(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​​

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[BSidesCF 2019]Pick Tac Toe

[BSidesCF 2019]Pick Tac Toe 首先进行常规的信息收集&#xff0c;尝试几次下三子棋后查看源码发现 此时只需要更改id为r的&#xff0c;将他改为X&#xff0c;我们就胜利了抓包发现&#xff0c;数据通过post提交参数为move&#xff0c;顺便再下一子&#xff0c;抓包更改为move…

迈向容错新时代!PASQAL发布最新技术路线图

内容来源&#xff1a;量子前哨&#xff08;ID&#xff1a;Qforepost&#xff09; 编辑丨慕一 编译/排版丨沛贤 深度好文&#xff1a;1200字丨8分钟阅读 近日&#xff0c;法国中性原子量子计算公司PASQAL发布了最新技术路线图&#xff0c;概述了其在硬件、业务场景用例及进一…

迁移学习的技术突破与应用前景

目录 前言1 迁移学习技术1.1 原理与分类1.2 主要挑战 2 迁移学习应用2.1 计算机视觉2.2 医疗健康 3 未来展望3.1 推动各领域发展3.2 提高模型泛化能力和效果3.3 在新兴领域中广泛应用 结语 前言 迁移学习作为机器学习领域的重要技术之一&#xff0c;以其能够将从一个任务中学到…

为什么延迟删除可以保证MYSQL 与redis的一致性?

看过很多保持MYSQL 与redis保持一致性的文章都提到了延迟删除&#xff0c;其实脱离任何业务场景的设计都是不切实际的&#xff0c;所以我会本着一个通用的读写场景去分析为什么延迟删除大概率可以保证MYSQL与redis的最终一致。 通常的读写场景 通常在使用redis作为读写缓存时…

蓝桥杯-02-2023蓝桥杯c/c++省赛B组题目

参考 2023 年第十四届蓝桥杯 C/C B组省赛题解 2023蓝桥杯c/c省赛B组题目(最全版)&#xff1a; A&#xff1a;日期统计 这题方法应该很多&#xff0c;没有和别人讨论想法。我的解法思路是&#xff1a;先 load 函数生成所有这一年的合法日期&#xff0c;然后枚举所有可以从数据…

嵌套循环实现九九乘法表

大家好&#xff1a; 衷心希望各位点赞。 您的问题请留在评论区&#xff0c;我会及时回答。 案例描述 利用嵌套循环&#xff0c;实现九九乘法表。 代码 #include <iostream> #include <Windows.h>using namespace std;int main(void) {//外层循环执行一次&#…

《计算机考研精炼1000题》为你考研之路保驾护航

创作背景 在这个充满挑战与竞争的时代&#xff0c;每一位考生在备战研究生考试的过程中&#xff0c;都希望通过更多符合考纲要求的练习题来提高自己的知识和技能。为了满足这一需求&#xff0c;我们精心策划和编辑了这本《计算机考研精炼1000题》。在考研政治和考研数学领域&a…

格密码从词根词缀和单词起源的角度来介绍一下,commit词根词缀分析:词义发展:现代用法举例:小结:nuance词根词缀分析:词义发展:现代用法举例:小结:

目录 格密码 从词根词缀和单词起源的角度来介绍一下&#xff0c;commit 词根词缀分析&#xff1a; 词义发展&#xff1a; 现代用法举例&#xff1a; 小结&#xff1a; nuance 词根词缀分析&#xff1a; 词义发展&#xff1a; 现代用法举例&#xff1a; 小结&#xff…

微服务高级篇(一):微服务保护+Sentinel

文章目录 一、初识Sentinel1.1 雪崩问题及解决方案1.2 微服务保护技术对比1.3 Sentinel介绍与安装1.4 微服务整合Sentinel 二、Sentinel的流量控制三、Sentinel的隔离与降级四、Sentinel的授权规则五、规则持久化5.1 规则管理模式【原始模式、pull模式、push模式】5.2 实现push…

B端界面不漂亮,所以搞不定客户,这就扯淡了。

在商业领域&#xff0c;产品的外观和用户体验确实对吸引和留住客户起着重要的作用。漂亮的界面设计可以提升用户对产品的好感和信任度&#xff0c;从而增加用户的使用和购买意愿。 虽然贝格前端工场致力于提升B端系统的感官和体验&#xff0c;但是我们依然认为界面美观不美观&…

c语言综合练习题

1.编写程序实现键盘输入一个学生的学分绩点 score&#xff08;合法的范围为:1.0—5.0&#xff09;&#xff0c;根据学生的学分绩点判定该学 生的奖学金的等级&#xff0c;判定规则如下表所示。 #include <stdio.h>int main() {float score;printf("请输入学生的学分…

解决jenkins运行磁盘满的问题

参考&#xff1a;https://blog.csdn.net/ouyang_peng/article/details/79225993 分配磁盘空间相关操作&#xff1a; https://cloud.tencent.com/developer/article/2230624 登录jenkins相对应的服务或容器中查看磁盘情况&#xff1a; df -h在102挂载服务器上看到是这两个文件…

c++的STL(5)-- set和multiset容器

set和multiset容器概述 首先set和multiset这两种容器底层是红黑树(是一种数据结构&#xff0c;是树形结构)实现的。我们前面说到vector,deque容器是插入数据慢&#xff0c;但是读取数据快&#xff0c;list呢恰恰相反&#xff0c;插入数据快&#xff0c;查询慢。但是set和multis…

【算法】双指针的应用

文章目录 前言1. 移动零&#xff08;easy&#xff09;2. 复写零&#xff08;easy&#xff09;3. 快乐数&#xff08;medium&#xff09;4. 盛水最多的容器&#xff08;medium&#xff09;5. 有效三角形的个数&#xff08;medium&#xff09;6.和为 s 的两个数字&#xff08;eas…

机器学习 - 准备数据

“Data” in machine learning can be almost anything you can imagine. A table of big Excel spreadsheet, images, videos, audio files, text and more. 机器学习其实可以分为两部分 将不管是什么data&#xff0c;都转成numbers.挑选或者建立一个模型来学习这些numbers …

json字符串的数据提取

json的数据提取 学习目标 掌握 json相关的方法(load loads dump dumps)了解 jsonpath的使用(提取 json中的数据) 2 复习什么是json JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式&#xff0c;它使得人们很容易的进行阅读和编写。同时也方便了机器进行解析和…

高效文件管理,批量复制文件夹名称 ,轻松提升工作效率

在信息爆炸的时代&#xff0c;电脑中的文件夹数量与日俱增&#xff0c;管理和整理这些文件夹成为一项繁琐的任务。您是否曾因为需要复制大量文件夹的名称而感到苦恼&#xff1f;现在&#xff0c;我们为您带来了一款能够一键批量复制文件夹名称的神奇工具&#xff0c;让您的效率…

【C语言进阶篇】自定义类型:结构体(下)

【C语言进阶篇】自定义类型&#xff1a;结构体&#xff08;下&#xff09; &#x1f308;个人主页&#xff1a;开敲-CSDN博客 &#x1f525;所属专栏&#xff1a;C语言_开敲的博客-CSDN博客 &#x1f33c;文章目录&#x1f33c; 1. 结构体传参 2. 结构体实现位段 2.1 什么是…

【CSS】html滚动条相关

1.滚动条样式 ::-webkit-scrollbar {width: 10px;height: 10px;z-index: 101; } ::-webkit-scrollbar-thumb {border-radius: 5px;background: #cecece; } ::-webkit-scrollbar-track {// background: #f5f5f5be;background: rgba(33, 85, 163, 0); } ::-webkit-scrollbar-but…

mysql - 多表访问

多表访问 创建两个表 mysql> SELECT * FROM t1; ------------ | m1 | n1 | ------------ | 1 | a | | 2 | b | | 3 | c | ------------ 3 rows in set (0.00 sec)mysql> SELECT * FROM t2; ------------ | m2 | n2 | ------------ | 2 | …