乐趣区

关于javascript:Mobx学习

Preface

  • MobX 曾经存在 6.0 版本

    • 相较于 5.0 版本,6.0 将默认禁用 5.0 版本的 decorate,它将应用新的 API 去代替 decorate. 参见:MobX EN 6.X

如果你想从 MobX 4.x/5.x 版本迁徙到 6.x 版本…唔,我晓得这在大型项目中很艰难,所以咱们提供了一个解决办法:MobX 6.x 版本(它在这段:Migrating an entire code-base from decorators to makeObservablemight be challenging

而应用非装璜器的起因除了它们不是规范的以外,还有因为装璜器无奈和 React Hook 一起联合应用,即:装璜器只能用于装璜 class component,而不是装璜函数式组件。

What’s the MobX ?

MobX 是一个 状态治理 library,;同样的,有类似作用的有:Redux,Vuex,它们都是对状态进行治理,只不过可能其实现过程不同。

其中 MobX 是通过通明的函数响应式编程 (transparently applying functional reactive programming – TFRP) 来对状态进行治理并使得状态治理变得简略和可扩大

MobX 背地的哲学:任何源自利用状态的货色都应该主动地取得。

TIP: React 和 MobX 是一对强力组合。React 通过提供机制把利用状态转换为可渲染组件树并对其进行渲染。而 MobX 提供机制来 存储和更新利用状态 供 React 应用。

Use MobX in React

  1. 首先须要装置 mobx 和 mobx-react

    TIP: mobx-react-lite 更实用于 react hook,即:没有 class components 的我的项目,尽管 mobx-react 曾经集成了 mobx-react-lite,然而它更轻便。

    下载应用:npm i --save mobx-react-lite

    # 倡议应用 yarn
    yarn add mobx --save
    yarn add mobx-react --save
    # or
    npm install --save mobx mobx-react
    • 值得一提的是:如果你齐全应用 MobX 6.x 版本,那么你可能不须要应用到以下的将装璜器编译,这是因为 MobX 6.x 版本将默认不启用装璜器,你也齐全不须要应用它。

  2. tsconfig.json 中启用编译器选项 "experimentalDecorators": true
  3. 装置 babel-preset-mobx 和 babel-plugin-transform-decorators-legacy

    npm i --save-dev babel-preset-mobx babel-plugin-transform-decorators-legacy

    并在 .babelrc 中增加以下配置:

    {
      "presets": [
          "mobx",
          "es2015", 
          "stage-1
      ],
      "plugins": ["transform-decorators-legacy"]
    }

    TIP:插件的程序很重要: transform-decorators-legacy 应该放在 首位

  4. 而后在文件中导入即可,如:

    import {observer} from "mobx-react";

    import {observable, computed, action} from "mobx";

参考文档:

  • Mobx- 启用装璜器语法

Api CN-MobX 5.x | EN-MobX 6.x

您能够购买 Mobx 6 的备忘单,它只有一页,然而它含有 Mobx 中所有重要的的 API,并且针对 MobX 6.x 进行了更新,包含 mobx-react/-lite.

makeObservable(MobX 6.x)

用法

  • makeObservable(target, annotations?, options?)

PS:makeObservable 只能用于 class component 中

介绍

target

makeObservable 能够用来捕捉现有对象的属性并使得这些属性成为可察看属性,咱们能够把任何 JS 对象包含 class 的实例(这和 observable 不同,observable 永远无奈察看 class 实例)传递到 makeObservable 的第 1 个参数 target 中,

但请留神:通常,咱们会把 makeObservable 用在 class 的 constructor,并使得它的第 1 个参数 target 为 this:

class A {constructor() {makeObservable(this,{}) 
    }
}

功能性并且具备参数的函数不须要任何正文,如:findUsersOlderThan(age: number): User[]

原文:Methods that derive information and take arguments (for example findUsersOlderThan(age: number): User[]) don’t need any annotation.

从 reaction 调用它们时,将会跟踪它们的读取操作,然而不会记住它们的输入,以防内存透露

原文:Their read operations will still be tracked when they are called from a reaction, but their output won’t be memoized to avoid memory leaks.

看看这个:MobX-utils computedFn {🚀}

annotations?

makeObservable 的第 2 个参数通常是一个对象,用来映射第 1 个参数对象(通常为 this)的每个属性,咱们为这些属性赋予 mobx 中的各种 api,标记着它们的用途:

// 在 class 中应用 makeObservable 的实例
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 的实例
// 留神:这里用的是 makeAutoObservable
import {makeAutoObservable} from "mobx"

function createDoubler(value) {
    return makeAutoObservable({
        value,
        get double() {return this.value * 2},
        increment() {this.value++}
    })
}
/**
 * NOTE:class 也能够利用 makeAutoObservable。* 这里之所以应用 makeAutoObservable,是为了演示这些示例之间的差别,仅仅阐明了如何将 MobX 利用于不同的编程款式。*/

options?

makeObservablemakeAutoObservable 它们的第 3 个参数的行为是一样的。

makeAutoObservable(Mobx 6.x)

用法

  • makeAutoObservable(target, overrides?, options?)

PS:makeAutoObservable 只能用于 class component 中

介绍

makeAutoObservable 相似于 steroids 上的 makeObservable

原文:makeAutoObservable is like makeObservable on steroids

因为它会默认推断所有属性,其推断规定为:Interference rules

然而你依然能够应用带有特定正文的 overrides 参数来笼罩默认的推断行为。

TIP:应用 makeAutoObservable 和应用 makeObservable 相比,makeAutoObservable 函数更加紧凑且易于保护,因为新成员不用明确提及。

Note

makeAutoObservable 无奈用在具备 super() 或子类上。

makeObservable VS makeAutoObservable

makeAutoObservable()makeObservable() 相比,其构造更加紧凑与容易保护,这是因为 new members don’t have to be mentioned explicitly(新成员不用明确提及)。

Howerver,在具备 super 或 subclassed(子类中)的 class 中,无奈应用 makeAutoObservable(),否则会在编译实现后运行报错:

Error: [MobX] 'makeAutoObservable' can only be used for classes that don't have a superclass,有如下示例:

class A {constructor() {makeAutoObservable(this) 
    }
};

class B extends A {constructor() {super()
        makeAutoObservable(this) //error,这会在编译实现后运行时失败
    }
}

参考文档

  • makeAutoObservable(Mobx 6.x)

CN-(@)observable|EN-observable

用法

  1. observable(source, overrides?, options?) EN-MobX 6.x
  2. observable(value) CN-MobX 5.x
  3. @observable classProperty = value CN-MobX 5.x

NOTE:MobX 6.x 中,@observable 是可选的。

以上的三种用法其行为是等价的,只不过 MobX 6.x 中,仿佛多了两个参数,我将会在前面解释。

在这里,笔者始终倡议应用第 1 种写法,它更为的平安以及够新(2020-12-11),或者说是规范。

介绍

在本节中,我将以 MobX 6.x 中的 observable 去介绍。

observable(source, overrides?, options?) MobX 6.x

source

当咱们调用 observable 函数时,向之传递 source 参数,那么 mobx 就会察看到整个 source 对象一次,并且 mobx 将会克隆你传过来的 source 参数对象(克隆的对象是一个 Proxy(代理)对象),同时会察看 source 对象的所有成员(察看 source,而非克隆 source 的对象),使它们变为可察看属性,这相似于 makAutoObservable 的实现。

简略来说:observable() 将返回一个源对象的 Proxy 对象,这意味着:你向 observable() 传递的 source 即便在将来又增加了成员,那么该成员也将主动的成为 mobx 的可察看成员(除非禁用了 Proxy 用法),

sure,咱们也能够向 observable() 中传入汇合类型,如:Set、Map、Array。同样的,mobx 也会克隆这些汇合类型(一个 Proxy)并将之转为可察看对象,即:这些汇合类型即便在将来增加了成员,这些增加的成员也将转为可察看成员。

overrides?

你能够提供一个 overrides 映射(override map)来为特定成员指定正文(annotations),这个行为相似于 makeAutoObservable:

makeObservable(this, {
    value: observable,
    double: computed,
    increment: action
})

// 直观示例
observable({setAge(){}},{setAge:action})

在以上的直观示例中,咱们应用向 observable 传入第 2 个参数(overrides)来扭转 MobX 的 默认正文推断规定,使得 observable 察看的对象中的 setAge() 的正文从 autoAction(默认)扭转为 action(自设置)。

options?

The above APIs take an optional options argument which is an object that supports the following options:

  • autoBind: true automatically binds all created actions to the instance.
  • deep: false uses observable.ref by default, rather than observable to create new observable members.
  • name: <string> gives the object a debug name that is printed in error messages and reflection APIs.

应用 done 排除属性或办法

你能够应用 false 把一个正在解决的属性或办法排除,PS:这须要你手动去做它,应用 done:boolean 去标记一个须要排除的属性或办法。

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")
/**
    In contrast to the first example with makeObservable, observable supports adding (and removing) fields to an object. This makes observable great for collections like dynamically keyed objects, arrays, Maps and Sets.
    与 makeObservable 的第一个示例不同,observable 反对向对象增加(和删除)字段。这使得 observable 非常适合于动静键控对象、数组、映射和汇合等汇合。*/

PS:这点在 makeAutoObservable 中 In particular false can be used...

Note

make(Auto)Observable and observable 的次要区别

make(Auto)Observable and observable 的次要区别是:observable 的第一个参数将接管一个须要被察看的对象,同时它还会创立这个可察看对象的克隆对象(Proxy 对象)。

第 2 个不同点就是:observable 会创立一个 Proxy 对象,来避免你把一个对象视作动静的查找映射(即:避免你在将来往这个对象上持续增加属性),这是因为创立的这个 Proxy 对象可能捕捉将来增加的属性。

简略来说:observable 会创立一个 Proxy 对象,来捕捉 [被代理对象] 当前可能增加的属性,使得你应用 observable 察看到的对象在当前增加属性时,这些属性也将是可察看的。

TIP:如果你想要使可察看的对象具备一个规定构造,并且你保障其中所有成员都已事后申明,那么咱们倡议应用 makeObservable,因为非代理对象的速度更快,并且非代理对象更加容易 debugger 和 console.log 中进行审查。

因而,在工厂函数中举荐应用 make(Auto)Observable API。

值得一提的是:将来有可能通过 {proxy: false} 作为一个 observable 的选项失去一个非代理的克隆。

根本类型和类实例无奈将之转为可察看的数据

observable 无奈将根本类型(原始值)和类实例(new class())转变成可察看的数据。

其中前者(根本类型转为可察看的数据)在 MobX 中,没有任何办法能够将之转为可察看对象;而后者(类实例)在 MobX 中存在办法(如:observe)将之转为可察看的数据,只是 observable 做不到罢了。

而 MobX 无奈把根本类型转为可察看的数据的起因很简略:在 JS 中,根本类型(原始值)是不可扭转的 /%E5%9F%BA%E6%9C%AC%E7%B1%BB%E5%9E%8B(%E5%8E%9F%E5%A7%8B%E7%B1%BB%E5%9E%8B).md#%E5%AE%9A%E4%B9%89),这条规定是定死的!

而把类实例传递给 observable 或一个曾经应用 observable 察看的对象作为其属性,都永远不会使得类实例成为一个可察看的数据是因为:让 class 中的成员转为可察看的状态是 class constructor 的责任。

且若你将一个可察看的对象(observable(obj))中的某个属性(它是一个根本类型的值或一个类实例)当作 props 传递给子组件,即便该子组件是响应式的(@observer class... / observer(class)),也无奈在你扭转 obj.value 时,去进行响应。

可用的办法

示例

组件中属性无奈更新?

Reference

  • EN-observable MobX 6.x
  • CN-observable MobX 5.x

Interference rules

概念

MobX 中存在本人的一套推断正文的规定。

简略来说就是:当你应用 observablemake[Auto]Observable 去察看一个对象时,那么 MobX 将对该对象中的属性、函数、get() 等进行默认正文,其中默认正文推断规定如下:

  1. 所有蕴含 function 值的成员(属性)都将应用 autoAction 进行正文。

    TIP:这是相似继承链的关系,即:对象的属性若是一个对象,在该子对象中若存在 function,则也会正文为:autoAction

  2. 所有 get 属性都将正文为:computed
  3. 所有其余字段都将正文为:observable
  4. [机翻]作为生成器函数的任何 (继承的) 成员都将被标注为“流”。(请留神,在某些 transpiler 配置中,生成器性能是无奈检测到的,如果流程没有按预期工作,请确保明确指定“流程”。)
  5. overrides 参数中将不会正文带有 false 的成员标记,如:应用只读字段(标识符)

原文:

  1. Any (inherited) member that contains a function value will be annotated with autoAction.
  2. Any getter will be annotated with computed.
  3. Any other own field will be marked with observable.
  4. Any (inherited) member that is a generator function will be annotated with flow. (Note that generators functions are not detectable in some transpiler configurations, if flow doesn’t work as expected, make sure to specify flow explicitly.)
  5. Members marked with false in the overrides argument will not be annotated. For example, using it for read only fields such as identifiers.

PS:在 makeAutoObservable 一节中,咱们曾经提到过!

示例

一个更直观且论述了 mobx 中(正文)推断规定的例子:

var person = observable({
    /**
     * mobx 会将以下的 3 个成员(属性)正文为:observable
     */
    name: "John",
    age: 42,
    showAge: false,
    
    // mobx 将推断该 get 的正文为:computed
    get labelText() {return this.showAge ? `${this.name} (age: ${this.age})` : this.name;
    },
    
    /** 动作
     * 原本 mobx 将会推断 setAge() 函数的正文为:autoAction,* 但因为咱们传递给 observable() 的第 2 个参数将之从新设置了正文
     * 所以此时 mobx 会将 setAge() 正文为:action
     */
    setAge(age) {this.age = age;}
}, {
    setAge: action
    // 其余属性默认为 observable / computed
});

Reactions(反馈) & Derivations(衍生)

CN-(@)observer(MobX 5.x) | EN-observer(MobX 6.x)

用法

  1.    class MyComponent extends React.Component {...})
       observer(MyComponent)
       // 等价于
       const MyComponent = () => observer( (prpos:any)=>{...}  ) // 倡议应用
  2.    // 装璜器是可选的!MobX 6.x 中也是如此。@observer
       class MyComponent extends React.Component {...})
  3. observer(React.createClass({ ...}))
  4. observer((props, context) => ReactElement)
  5. observer(class MyComponent extends React.Component { ...}+

以上 5 中用法其成果是等价的,然而后面两种是最常见的用法。

不过在这里,笔者始终倡议应用第一种,即:不要在 MobX 中应用装璜器(这是非规范的,参见:从 MobX 4/5 迁徙到 6)

PS:也请依据理论状况抉择 Mobx 的相应版本。

介绍

observer 函数 / 装璜器能够用来将 React 组件转变成响应式组件。相似于 class 的组件中应用 makeAutoObservable(this) 使得以后 class 组件的实例变为响应式的。

它用 mobx.autorun 包装了组件的 render 函数,以确保当任何组件的渲染中(render 中)应用的 [被察看的数据] 变动时都能够强制刷新组件。

简略来说就是:当组件的 render 中存在的 [被察看的状态] 产生扭转时,将会强制刷新组件,即:从新执行一次 render,这和 setState() 或 useState Hook 的行为统一,它们都会从新刷新组件(从新执行一次 render)。

并且 observer 还会确保当组件中的 [被察看的状态] 没有产生扭转时,组件不会从新进行渲染,因而:即便组件中存在[被察看的状态],然而该状态从未扭转过,那么该组件将永远不会因为这个状态导致从新渲染。

observer 不关怀可察看状态是怎么存于组件中的,它只须要读取组件中可察看状态即可。

Note 留神点

observermobx-react 包中的

observer 是由 独自的 mobx-react or mobx-react-lite(mobx-react 集成 mobx-react-lite)包提供的。

确保 observer 是第 1 个装璜器或函数

observer 须要组合其它装璜器或高阶组件时,请确保 observer 是最深处 (第一个利用) 的装璜器,否则它可能什么都不做。

禁止将 observervable 的值传递到非 observer 组件

不要将应用 observable 察看到的值传递到一个非 observer 的组件中。参见:MobX 6.x-Don’t pass observables into components that aren’t observer

class Todo {
    title = "test"
    done = true
    constructor() {makeAutoObservable(this)}
}

const TodoView = observer(({todo}: {todo: Todo}) =>
   // 谬误!GridRow 组件将不会因为 todo.titie 或 todo.done 扭转从而从新渲染
   return <GridRow data={todo} />

   /** 正确!让 TodoView 组件查看 Todo 可察看状态的扭转,并传递 JS 原始数据构造。*/
   return <GridRow data={{
       title: todo.title,
       done: todo.done
   }} />

   // 正确,应用工具函数 'toJs' 当然也是好的,然而前一个更为简略直白,工具函数 'toJs' 参见:return <GridRow data={toJS(todo)} />
)
  • EN-toJS-MobX 6.x
  • CN-toJS-MobX 5.x

以上示例中正确的起因:您能够看看此处,以及这么做的起因。

/*
 * 这是因为当 observable 等函数去装璜状态(数据)时,会返回一个 Proxy 对象,而 mobx 
 * 正是通过这个 Proxy 去监听数据扭转并作出对应解决的。* 然而你如果将一个 Proxy 对象传递给一个非 observer 的 React 组件这是没有任何用途的,* 所以你须要以一般的 JS 数据结构传递给非 observer 组件,使得包裹非 observer 组件的
 * observer 组件能察看在非 observer 组件上的变动,而不是让非 observer 组件本人去察看!* 这样,能力使得 observable 值更新时,非 observer 组件也能进行从新渲染。*/
observer 组件拜访其余模块中的 observable

当咱们应用 observer 去使一个 React 组件变成响应式时,那么在该响应式组件的渲染函数中(可能是 class 组件的 render 或函数式组件的 return),

若存在以后模块(以后组件所属文件)的可察看状态,那么若该可察看状态扭转,则响应式组件的的渲染函数将会强制从新执行。

若还存在其余模块(其余文件)的可察看状态(如:通过 inject 注入过去的可察看数据),那么若咱们间接在渲染函数中应用其余模块的可察看状态(如:props.store(拜访 inject 注入的 stroe)),

即便其余模块的可察看状态通过某种形式扭转,以后响应式组件也并不会强制从新执行渲染函数。

这通常是因为你在 observer 组件的渲染函数中拜访的是其余模块的 observable 函数返回的 Proxy 对象,而非间接拜访其余模块的 observable 值,如:

// store.tsx
class Store {todos: any = [] // 寄存所有 list
    constructor() {makeAutoObservable(this)}
}
const store = new Store()
export default store
// a.tsx 批改前
const TodoHeader = inject('store')(observer((props: any) =>(<div> {console.log(props.store.todos)}</div>)
))

a.tsx 中,当 props.store.todos 产生扭转时,TodoHeader 并不会从新渲染。

这是因为:TodoHeader 的渲染函数中拜访的是 props.store.todos 的 Proxy 对象,而不是其自身,然而对于 MobX 来说,只有应用 observable 间接察看的 value 能力使得 Reactions 进行响应。

所以天然的,这里的 TodoHeader 并不会从新渲染;若要让它从新渲染,则必须使得 TodoHeader 拜访到 store.todos 的自身,而非 Proxy 对象,做如下更改:

// a.tsx 批改后
const TodoHeader = inject('store')(observer((props: any) =>(<div> {console.log(...props.store.todos)}</div>)
       // 上行做法有时也无奈触发组件响应,可能是因为一些其余起因,所以有更好的写法,即:拜访 store.todos 下的具体的某个 observable 值。(props: any) =>(props.store.todos.map((todo) => {console.log(todo.finished) }))
))
应该将 observer 用于有拜访 observable value 的所有组件

在应用 observer 装璜 / 包裹组件时,应该确保该组件的渲染函数中有拜访某个 observable,否则咱们不应该应用 observer.

参考文档

  • [CN-[@]observer](https://cn.mobx.js.org/refgui… 5.x)
  • EN-observer(MobX 6.x)
  • https://cn.mobx.js.org/refgui…

CN-(@)autorun(MobX 5.x) | EN-autorun(MobX 6.x)

用法

  1. autorun(() => { sideEffect}, options?) MobX 5.x

    options?

  2. autorun(effect: (reaction) => void) MobX 6.x

    实际上应用起来和 MobX 5.x 是一样的,参见:MobX 6.x Example

介绍

autorun 创立一个响应式函数:当提供的函数中,可察看的状态扭转时,就调用的响应式函数。

autorun 次要是用来执行 启动成果 (initiating effects) 的一个函数,记住 :永远不要用 autorun 去产生一个新值,这是 computed 该做的事件。

应用 autorun 时,会立刻调用一次提供的函数。

autorun 函数只会察看[提供的函数中所应用的数据],即:autorun 自身尽管会返回一个函数,然而调用它是有效的:

var numbers = observable([1,2,3]);
var sum = computed(() => numbers.reduce((a, b) => a + b, 0));

// 输入 '6',这是因为应用 autorun 时,会立刻调用一次提供的函数。var disposer = autorun(() => console.log(sum.get()));
numbers.push(4); // 输入 '10'


// 调用 autorun 返回的函数没有任何用途
disposer(); 
// 不会再输入任何值。`sum` 不会再从新计算。numbers.push(5);

CN-reaction | EN-reaction

用法

  1.    // MobX 5.x https://cn.mobx.js.org/refguide/reaction.html#reaction
       reaction(() => data, // 称之为:data funciton
           (data, reaction) => {sideEffect}, // 称之为:effect function
           options?
       )
  1.    // MobX 6.x https://mobx.js.org/reactions.html#reaction
       reaction(() => value, // 称之为:data funciton
           (value, previousValue, reaction) => {sideEffect}, // 称之为:effect function
           options?
       )

以 MobX 5.x 介绍

reaction 接管 3 个参数(2 个函数,1 个配置选项):

  1. data funciton

    data function 用来追踪 observable value,并返回一个 data(你想要返回的任何数据),而后此 data 将会作为 effect function 的第 1 个参数,

  2. effect function:function(data, reaction)

    effect function 接管 2 个参数:

    1. data

      data function 的返回值

    2. reaction

      以后的 reaction,能够用来在执行期间清理 reaction

    effect function 是用来执行动作的,且 effech funciton 仅仅只对 data function 中拜访(存在)的 observable 做出反馈。

  3. options?(MobX 5.x)

返回值:返回一个清理函数

介绍

reaction 相似于 autorun,但不同的是:对于如何追踪 observable 的数据提供了更为细粒度的管制。

reaction 的第 1 个参数函数的返回值只有扭转(一开始赋予的初始值不算扭转其返回值),则第 2 个参数函数就会执行;若第 1 个参数函数的返回值永远未扭转,则第 2 个参数就永远不会执行。

示例

import {observable, action, reaction, computed} from 'mobx';
import {observer} from "mobx-react";
import {Component} from 'react';
import ReactDOM from "react-dom";
// 察看一个对象
let obj = observable({hungryLevel: 100,})
// 使得 Animal 变成响应式的
@observer
class Animal extends Component {constructor(props: any) {super(props)
        // reaction 也能够放在组件里面
        reaction(() => this.isHungry,
            isHungry => {
                // 如果饥饿程度 < 50,则输入:我饿了
                if (isHungry) {console.log("我饿") }
                else {console.log("我不饿") }
                console.log("目前饥饿程度:", obj.hungryLevel)
            })
    }
    // 饥饿程度 累减 10
    @action reduceEnergy() { obj.hungryLevel -= 10}
    // 当饥饿程度 < 50,则返回 true(我饿了),反之我不饿
    @computed get isHungry() { return obj.hungryLevel < 50}
    
    render() { return (<div></div>) }
}
const giraffe = new Animal("")
console.log("当初开始扭转可察看状态:obj.hungryLevel")
for (let i = 0; i < 10; i++) {giraffe.reduceEnergy()
}
ReactDOM.render(
    // <Computed />,
    <Animal />,
    document.getElementById('root')
);
/** 将会输入:* 当初开始扭转可察看状态:obj.hungryLevel
 * 我饿
 * 目前饥饿程度: 40
 */

CN-when | EN-when

用法

  1.    // MobX 5.x
       when(predicate: () => boolean, 
           effect?: () => void, 
           options?
       )
  2.    // MobX 6.x
       when(predicate: () => boolean, 
           effect?: () => void, 
           options?
       )
  3.    // MobX 6.x
       when(predicate: () => boolean, 
           options?
       ): Promise

以上 3 种用法都是常见的,只不过第 1 种是 MobX 5.x 的,而第 2、3 种用法属于 MobX 6.x。

介绍

when 将始终察看给定的 predicate function,当 predicate function 的返回值为 true 时,effect function 就会主动执行。

when 函数将会返回一个 handler,你能够应用这个 handler 去手动使得 when 不再察看 predicate function

有意思的是,若你不提供第 2 个参数:effect function,那么 when 函数将会返回一个 Promise,

审慎应用 Reactions

了解 Reactions

不论是 observerreaction,以及 autorun 等 Reactions,它们都只会对 observable / 可察看状态 做出响应,其中存在的一些坑是十分危险的,它们可能会导致你的程序不会预期执行。

你能够看看这个大节:observer 组件拜访其余模块中的 observable

CN-(@)computed(MobX 5.x) | EN-computed(MobX 6.x)

用法

  1. computed(() => expression)
  2. computed(() => expression, (newValue) => void)
  3. computed(() => expression, options)
  4. @computed({equals: compareFn}) get classProperty() { return expression;}
  5. @computed get classProperty() { return expression;}

    以下为 MobX 6.x 的用法:参见这里

  6. computed (annotation)
  7. computed(options) (annotation)
  8. computed(fn, options?)

介绍

computed 专门用来依据现有的状态(通常指:observable value)或其余计算值 衍生出一个新值的

computed 是高度优化过后的,纵情随便应用,不必放心性能问题;

留神:不要将 computedautorun 搞混,只管它们都是响应式调用的表达式;即:若你想产生一个能够用于 observer 的新值,则请应用 computed;但若你并不想产生一个新值,而只是想响应式的达到一个成果,则应用 autorun,如:打印日志、发送网络申请等的 effect.

应用 computed 润饰过的表达式(通常是一个计算属性(属性函数),如:get/set)将是响应式的,当 computed 的 expression 中存在可察看的状态扭转时,则 computed 整个表达式将会执行,而如果 computed 的 value 中不存在或存在的可察看的状态未产生扭转,则 computed 表达式将不会执行——这就是所谓的响应式。

:但凡应用 computed 润饰的 value 都无奈枚举,因为计算属性是不可枚举的!

示例

// MobX 5.x
import {observable, computed} from "mobx";

class OrderLine {
    @observable price = 0;
    @observable amount = 1;
    constructor(price) {this.price = price;}
    
    // 计算属性;会产生新值
    @computed get total() {return this.price * this.amount;}
}

以上示例能够改成 decorate 形式:

// MobX 5.x
import {decorate, observable, computed} from "mobx";

class OrderLine {
    price = 0;
    amount = 1;
    constructor(price) {this.price = price;}

    get total() {return this.price * this.amount;}
}
decorate(OrderLine, {
    price: observable,
    amount: observable,
    total: computed
})

咱们也能够应用 MobX 6.x 书写此示例:

// MobX 6.x,注:6.x 版本中,decorate 已移除
import {observable, computed, makeAutoObservable} from "mobx";

class OrderLine {
    price = 0;
    amount = 1;
    constructor(price) {
        this.price = price;
        makeAutoObservable(this,{
            price:observable,
            amount:observable,
            total:computed
        })
    }

    get total() {return this.price * this.amount;}
}

Action(MobX 5.x | MobX 6.x)

CN-(@)action(MobX 5.x) | EN-action(MobX 6.x)

用法

  1. action(fn)
  2. @action.bound(function() {}
  3. @action classMethod() {}
  4. action(name, fn)
  5. @action.bound classMethod() {}
  6. @action(name) classMethod () {}
  7. @action boundClassMethod = (args) => {body}
  8. @action(name) boundClassMethod = (args) => {body}
  9. action (annotation) MobX 6.x

以上的用法都是罕用的用法,你能够任意抉择你喜爱的。

介绍

咱们将扭转利用状态的动作称之为“行为”,由此可见,任何利用都有行为。

在 MobX 中,所有行为都应该应用 action() 或 @action 将之包裹 / 正文,当然即便你不这么做,MobX 仅仅只会警示你,而不会让你程序编译失败,然而这并不是好行为。

应用 Action 让你代码更加易浏览,清晰,代码构造更优!并且应用 Action 会给你无效的提示信息。

Action 返回一个应用 untracked , transaction and allowStateChanges 包裹的函数。

async 行为 和runInAction

action 只影响 以后运行 的函数,不是以后被 调度(不是调用)的函数。

如:一个setTimeout,promise .thenasync 结构,在这些回调函数中将有状态扭转,那么这些回调应该应用 action 包裹 / 正文 =>

// 扭转状态的行为,应该应用 action 包裹,留神:并非包裹 setTimeOut,否则 setTimeout 将不会运行 
setTimeout(action(()=>{ 
    this.setState({
        name:'yomua',
        time:'2020-12-21 18:20'
    })
}),1000)

action.bound(MobX 6.x)

runInAction(MobX 6.x)

应用 flow 代替 async/await(可浏览常识)(MobX 6.x)

Cancelling flows(MobX 6.x)

tool-funciton API of mobx-react

inject CN-inject | EN-inject

用法

  1.    @inject("注入的属性名")
       MyComponent:你的组件
  2.    @inject(callback)   callback 根本用法:(allStore,nextProps?,nextContext?)=>additionalProps
       MyComponent:你的组件

    callback 接管 3 个参数,且本身返回一个对象(additionalProps),该对象中的属性就是咱们能在组件中应用 this.props.propName 拜访到的注入的属性的值

    1. allStore

      将所有可用的属性 (离以后应用 @inject 最近的 Provider 组件中的属性) 放入到该参数对象中

    2. nextProps?
    3. nextContext?

    返回值:

    {value1:(allStore as any).Provider 组件上的属性名
        value2:(allStore as any).Provider 组件上的属性名
        ...
    }

    小示例:

    @inject(
        allStore=> {value1:(allStore as any).Provider 组件上的属性名,
            value2:(allStore as any).Provider 组件上的属性名
        }
    )
    class A extends Component<{...}> {render() {
            return(
                <div>
                    {this.props.value1}
                    {this.props.value2}
                    ...
                </div>
            )
        }
    }
  1.    @inject("store1", "store2") 
       @observer 
       MyComponent:你的组件

    这种是 @inject@observer 的组合写法。

    请记住:@inject 始终在最里面,因为它属于内部装璜,而 @observer 属于外部装璜。

  2. inject("store")(observer(MyComponent))

在以上用法中,前两种是最常见的用法,第 3 个则是 @inject@observer 的组合写法,第 4 个则是不必装璜器的写法。

笔者始终倡议应用第 4 种写法,因为它够简洁,而且它是最新的(对于 MobX 6.x 来说)

介绍

mobx-react 提供的 inject 函数实现了 React 提供的 Context 机制,它能够让咱们在一个组件树中,不用使得两头组件帮忙传递数据,就能使的顶层间接传递数据给底层,

其用法为:应用 mobx-react 提供的 Provider 组件,将之作为一个组件的根组件,那么 Provider 组件包裹的组件以及一系列相干组件都能通过 inject,将 Provide 组件上的属性注入到组件树的任意组件中

从而在组件中通过 this.props.Provider 属性名 拜访 Provider 组件的属性。

上面看看这一个简略的示例吧:应用 Provider 和 inject 实现数据传递

示例

应用 Provider 和 inject 实现数据传递(MobX 6.x)

环境:

  "dependencies": {
    "mobx": "^6.0.4",
    "mobx-react": "^7.0.5",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.1",
    "typescript": "^4.1.3",
  },
import React, {Component} from 'react';
import {makeAutoObservable} from 'mobx';
import {Provider, inject} from 'mobx-react';
import ReactDOM from 'react-dom';
// 数据结构(Store)class Todo {
    todos = [{ id: 1, title: '工作 1', finished: false},
        {id: 2, title: '工作 2', finished: false},
    ];
    constructor() { makeAutoObservable(this) }
    // computed 属性(用来判断)get unfinishedTodoCount() { return this.todos.filter(todo => !todo.finished).length; }
}
let todoList = new Todo(); // 获取 Store
// todoList 将能够通过 @inject 注入 <Yomua /> 一系列相干的组件树中
const Testinject = () => (<Provider todoList={todoList}><Yomua /></Provider>)
const Yomua = () => (<ZY />) // 两头组件 
const ZY = () => (<YHW />) // 两头组件
/** 应用 @inject(callback) 将 Provider 上的所有可用 Store 通过提炼之后注入到 YHW 组件中
 * 留神:@inject 只能用于 class,或者说装璜器只能用于 class 组件。* 应用 @inject('todoList') 这种形式也能够
 */
@inject(allStore => (
    // 能够通过 this.props.todoList 就能拜访到 Provider.todoList 属性。{todoList: (allStore as any).todoList }
))
class YHW extends Component<{todoList?: any}> {render() {
        // 返回 Todo.title
        return (<div>{this.props.todoList.todos.map(todo => { console.log(todo.title) })}</div>)
    }
}
export default Testinject;
ReactDOM.render(
    <Testinject />,
    document.getElementById('id')
)
/** 控制台将输入:*     工作 1
     工作 2
 */

在以上示例中,显然的,咱们的组件树为:Provider 包裹 Yomua -> ZY -> YHW,而咱们的目标是:将 Provider 上的 todoList 间接传递到 YHW 中,使得两头组件(Yomua、ZY)不须要帮忙传递。

在这里,咱们奇妙地利用了 mobx-react 提供的 inject 函数实现了这个目标。

即:咱们应用 Provider 将 Yomua 包裹,使得 Provider 上的所有属性都能通过 inject 传递给 Yomua 的一系列相干组件,而后咱们在想要注入 Provider 的属性的组件那,应用 @inject()/inject() 将 Provider 的属性注入到以后组件,这样,在以后组件的 JSX 中就能够应用 {this.props.xx} 拜访 Provider 的属性!

真是不便的解决方案!

然而请记住,在应用 Provider 前无妨思考组合组件?因为 Context 机制会使得组件复用性变差!

应用 inject 来实现一个简略示例(非 @inject)
// 将 stroe 注入到 ComponentName 组件中,留神:这是规范的写法!const ComponentName = inject('store')(
    // observer 是必要的
    observer((props: any) => (<div>...</div>)
    )
)

Reference

  • CN-inject
  • EN-Provider and inject

Pit

在 MobX6.x 中打消应用装璜器的正告

正告提醒:xxx 是新提案,当前可能删除。打消它:

  1. 在 setting.json 中增加:

    "javascript.implicitProjectConfig.experimentalDecorators": true,
  2. 而后在我的项目外部的 tsconfig.json(若没有则新建)中的 compilerOptions 中增加:

    "compilerOptions":{
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
  3. 最初转到 IDE 的 settings(preferences – settings 或 ctrl+,),搜寻 experimentalDecorators,在显示的界面中,选中复选框,将之打勾

TIP:你可能须要重启 IDE => ctrl+shift+p 搜寻 reload window,而后点击它即可。

留神:即便打消了正告,然而因为 Mobx 5.x 及以下版本应用的装璜器都是可能在将来被扭转,所以想要一劳永逸的解决,倡议应用:MobX 6.x 版本

留神:在 MobX 6.x 中,decorator 曾经从新增加,这意味着你依然能够在 MobX 6 中应用装璜器,参见:Mobx 6.x EN- 第 5 点(For Typescript users)

参考文档:

  • Experimental decorators warning in TypeScript compilation
  • How to restart VScode after editing extension’s config?
  • MobX 6.x

MobX 6.x 中删除了 decorate

decorate API 已在 MobX 6 中删除,须要 makeObservable 在指标类的构造函数中替换。makeObservable 承受雷同的参数。

Mobx and Redux

Reference

  • Rdux 和 Mobx 区别
  • Mobx 较为灵便,Redux 有点相似于 Vuex

Translate

we now recommend mobx-react-liteover mobx-reactfor (greenfield) projects that don’t use class components

=> 对于不应用 class components 的我的项目, 咱们举荐应用 mobx-react-lite,而非 mobx-react

observable

概念和用法翻译

  • observable(source, overrides?, options?)

能够把 observable 正文作为一个函数调用,以便使得 mobx 立刻察看到整个对象。

mobx 将会克隆 observable() 中的第一个参数:source 对象,并且 mobx 会察看 source 对象的所有成员(察看 source,而非克隆 source 的对象),这相似于 makAutoObservable 的实现。

同样的,你能够提供一个 overrides 参数映射(override map)来指定特定成员的正文。

observable() 将返回一个 Proxy 对象,这意味着:你向 observable() 传递的 source 即便在将来又增加了成员,那么该成员也将主动的成为 mobx 的可察看成员(除非禁用了 Proxy 用法)

也能够向 observable() 中传入汇合类型,如:Set、Map、Array,同样的,mobx 也就克隆这些汇合类型(一个 Proxy)并将之转为可察看对象,即:这些汇合类型即便在将来增加了成员,这些增加的成员也将转为可察看成员。

TIP 翻译

make(Auto)Observable and observable 的次要区别是:observable 的第一个参数将接管一个须要被察看的对象,同时它还会创立这个可察看对象的克隆。

第 2 个不同点就是:observable 会创立一个 Proxy 对象,来避免你把一个对象视作动静的查找映射,这是因为创立的这个 Proxy 对象可能捕捉将来增加的属性。

简略来说:observable 会创立一个 Proxy 对象,来捕捉被代理对象当前可能增加的属性,使得你应用 observable 察看到的对象在当前增加属性时,这些属性也将是可察看的。

如果你想要使可察看的对象具备一个规定构造,并且其中所有成员都是事后申明的,那么咱们倡议应用 makeObservable,因为非代理对象的速度更快,并且非代理对象更加容易 debugger 和 console.log 中进行审查。

因而,在工厂函数中举荐应用 make(Auto)Observable API,请留神:将来有可能通过 {proxy: false} 作为一个 observable 的选项失去一个非代理的克隆。

Reference

  • MobX CN 5.X => 中文文档目前只更新到 5.x | 2020-12-09
  • MobX EN 6.X

    • MobX-6
    • Announcing mobx 6(官网公布)
    • Proposal: About drop decorators

      PS: 装璜器最初并没有从 MobX 6.x 中删除,它只是默认禁用了。你能够详见此处开启它:打消新提案(装璜器)的正告

退出移动版