Mobx4.X状态管理入门

前言

本来说接下来会专一学nodejs,可是最新工做又学习了一些有意思的库,於是就再写下来作个简单的入门,以前我写过一篇文章,这个也算是做為一个补充吧. javascript

此次无非就是相似笔记,把认为的一些关键点记下来,有些地方还没用到就衹是描述一下,代码有些本身写的,有些文档写的很好就搬下来,想瞭解更多可看官网
Mobx中文文档
Mobx英文文档
Github 仓库 html

PS:
2018/06/11 发现中文版有些关键地方没翻译,补充一下observable({})/observable.object(props, decorators?, options?)部分java

Mobx4.X

经过透明的响应式编程使状态管理变得简单和可扩展,背后哲学是任何源自应用状态的东西都应自动得到,(包括UI,数据序列化,服务器通信等)node

React经过提供机制把应用状态转换成可渲染组件树并对其渲染,优化UI渲染,就是经过使用虚拟DOM减小昂贵的DOM变化数量.
Mobx提供机制来存储和更新应用状态供React使用,优化应用状态和React组件同步,经过使用响应式的虚拟依赖状态图表,让其在须要的时候才更新而且保持最新.react

我的感受与Redux相比除了目的一致是管理应用状态以外.无论是写法仍是思想都大相径庭.因為才刚入门,这裡只说用法不讲区别.git

官方代码

咱们先看看这段代码作了什麼,再分开详细讲解一下对应知识点github

  • observable(组件): 转成响应式组件,会自动转换应用状态和更新;
  • get 函数: 计算值,根据现有的状态或其它计算值衍生出的值;
  • autorun函数: 相似get,依赖关系改变时触发;
  • action: 改变状态,严格模式下全部修改操做都应该在action裡面执行;
import {observable, autorun} from 'mobx';

var todoStore = observable({
  /* 一些观察的状态 */
  todos: [],

  /* 推导值 */
  get completedCount() {
    return this.todos.filter(todo => todo.completed).length;
  },
});

/* 观察状态改变的函数 */
autorun(function() {
  console.log(
    'Completed %d of %d items',
    todoStore.completedCount,
    todoStore.todos.length
  );
});

/* ..以及一些改变状态的动做 */
todoStore.todos[0] = {
  title: 'Take a walk',
  completed: false,
};
// -> 同步打印 'Completed 0 of 1 items'

todoStore.todos[0].completed = true;
// -> 同步打印 'Completed 1 of 1 items'

官方流程图:
图片描述express

State(状态)

observable

//标準用法
observable(value)
//装饰器用法
@observable classProperty = value

Observable 值能够是JS基本数据类型、引用类型、普通对象、类实例、数组和映射. 匹配类型应用了如下转换规则,但能够经过使用调节器进行微调.编程

  • Map: 返回一个新的 Observable Map,不但对一个特定项的更改作出反应,并且对添加或删除该项也作出反应;
  • 数组: 会返回一个 Observable Array;
  • 没有原型的对象: 那么对象会被克隆而且全部的属性都会被转换成可观察的;
  • 有原型的对象: JavaSript 原始数据类型或者函数,observable会拋出错误,若是你想要建立一个独立的

observable引用例如值可使用Boxed Observable observables.MobX 不会将一个有原型的对象自动转换成可观察的,由于这是它构造函数的职责.能够在constructor使用extendObservable或者类型定义使用decorate替代.json

observable(new Map())/observable.map(values, options)

values: 能够是对象、 数组或者字符串键的 ES6 map;
options:

  1. deep: 决定分配给 observable 映射的值会否经过 observable 来传递使其转变成可观察的;
  2. name: 调试名称,用于 spy 或者 MobX 开发者工具;
const map = observable.map(new Map());

如下是MobX 提供方法:

  • toJS(): 将 observable 映射转换成普通映射;
  • toJSON(): 返回此映射的浅式普通对象表示.(想要深拷贝,请使用 mobx.toJS(map));
  • intercept(interceptor): 能够用来在任何变化做用于映射前将其拦截;
  • observe(listener, fireImmediately?): 注册侦听器,在映射中的每一个更改时触发;
  • merge(values): 把提供对象的全部项拷贝到映射中.values 能够是普通对象、entries 数组或者 ES6 字符串键的映射;
  • replace(values): 用提供值替换映射所有内容.是 .clear().merge(values) 的简写形式;

observable([])/observable.array(values, options)

这是递归的,因此数组中的全部(将来的)值都会是可观察的.
options:

  • deep: 决定分配给 observable 映射的值会否经过 observable 来传递使其转变成可观察的;
  • name: 调试名称,用于 spy 或者 MobX 开发者工具;
const ary = observable.array([1, 2, 4]);

注意:
observable.array 会建立一我的造数组(类数组对象)来代替真正的数组. 支持全部的原生方法,包括从索引的分配到包含数组长度.

  • 验证类型方法的话返回不是数组.能够经过使用 array.slice() 在 observable 数组传递给外部库或者内置方法前建立一份浅拷贝;
  • sort 和 reverse 函数实现不会改变数组自己,而是返回一个排序过/反转过的拷贝;

如下是MobX 提供方法:

  • intercept(interceptor): 能够用来在任何变化做用于数组前将其拦截;
  • observe(listener, fireImmediately? = false): 监听数组的变化.回调函数将接收表示数组拼接或数组更改的参数,它符合 ES7 提议.它返回一个清理函数以用来中止监听器;
  • clear(): 从数组中删除全部项;
  • replace(newItems): 用新项替换数组中全部已存在的项;
  • find(predicate: (item, index, array) => boolean, thisArg?): 基本上等同于 ES7 的 Array.find 提议;
  • findIndex(predicate: (item, index, array) => boolean, thisArg?): 基本上等同于 ES7 的 Array.findIndex 提议;
  • remove(value): 经过值从数组中移除一个单个的项.若是项被找到并移除的话,返回 true ;
  • peek(): 和 slice() 相似,返回一个有全部值的数组而且数组能够放心的传递给其它库,可是不建立保护性拷贝;

observable({})/observable.object(props, decorators?, options?)

一个普通的 JavaScript 对象 (指不是使用构造函数建立出来的对象,而是以 Object 做为其原型,或者根本没有原型)传递给 observable 方法,对象的全部属性都将被拷贝至一个克隆对象并将克隆对象转变成可观察的.
这是递归应用的,因此若是对象的某个值是一个对象或数组,那么该值也将经过 observable 传递.
options:

  • deep: 决定分配给 observable 映射的值会否经过 observable 来传递使其转变成可观察的;
  • name: 调试名称,用于 spy 或者 MobX 开发者工具;
const obj = observable.object({ key: "value"});

注意:

  • [MobX 4及如下]当经过 observable 传递对象时,只有在把对象转变 observable 时存在的属性才会是可观察的. 稍后添加到对象的属性不会变为可观察的,除非使用 set 或 extendObservable;
  • 只有普通的对象能够转变成 observable .对于非普通对象,构造函数负责初始化 observable 属性. 要么使用 @observable 注解[color=#b1b1b1](annotation,这个解释不太懂??),要么使用 extendObservable 函数;
  • 属性的 getter 会自动转变成衍生属性,就像 @computed 所作的;
  • observable 是自动递归到整个对象的.在实例化过程当中和未来分配给 observable 属性的任何新值的时候.Observable 不会递归到非普通对象中;
  • 更细粒度的控制,好比哪些属性应该转变成可观察的和如何变成可观察的,请参见装饰器;

observable.box(value)

JavaScript 中的全部原始类型值都是不可变的,所以它们都是不可观察的,box建立一个基于 ref 装饰器的箱子.这意味着箱子里的任何(未来)值都不会自动地转换成 observable .
options:
1) name: 调试名称,用于 spy 或者 MobX 开发者工具;

const box = observable.box('box');
box.observe(function(change) {
  console.log(change.oldValue, '->', change.newValue);
});

如下是box提供方法:

  • get(): 返回当前值;
  • set(value): 替换当前存储的值并通知全部观察者;
  • intercept(interceptor): 能够用来在任何变化应用前将其拦截;
  • observe(callback: (change) => void, fireImmediately = false): 注册一个观察者函数,每次存储值被替换时触发.返回一个函数以取消观察者.change是一个对象,其中包含 observable 的 newValue 和 oldValue .

装饰器

定义 observable 属性的行为,默认为对任意键值对使用 observable.deep,对 getters 使用 computed .

  • observable: observable.deep 的别名
  • observable.deep: 任何 observable 都使用的默认的调节器.它将任何(还没有成为 observable )数组,映射或纯对象克隆并转换为 observable 对象,并将其赋值给给定属性
  • observable.ref: 禁用自动的 observable 转换,只是建立一个 observable 引用
  • observable.shallow: 只能与集合组合使用. 将任何分配的集合转换为 observable,但该集合的值将按原样处理
  • observable.struct: 就像 ref, 但会忽略结构上等于当前值的新值
  • computed: 建立一个衍生属性, 参见 computed
  • computed(options): 同 computed , 可设置选项
  • computed.struct: 与 computed 相同,可是只有当视图产生的值与以前的值结构上有不一样时,才通知它的观察者
  • action: 建立一个动做, 参见 action
  • action(name): 建立一个动做,重载了名称
  • action.bound: 建立一个动做, 并将 this 绑定到了实例
class Person {
  name = 'John';
}
// 使用 decorate 时,全部字段都应该指定 (毕竟,类里的非 observable 字段可能会更多)
decorate(Person, {
  name: observable,
});

Derivations(衍生)

任何源自状态而且不会再有任何进一步的相互做用的东西就是衍生,衍生以多种形式存在:

  • 用户界面
  • 衍生数据,好比剩下的待办事项的数量.
  • 后端集成,好比把变化发送到服务器端.

MobX 区分两种类型的衍生:

  • Computed values(计算值): 它们是永远可使用纯函数(pure function)从当前可观察状态中衍生出的值;
  • Reactions(反应): Reactions 是当状态改变时须要自动发生的反作用.须要有一个桥梁来链接命令式编程(imperative programming)和响应式编程(reactive programming).或者说得更明确一些,它们最终都须要实现I / O 操做;

(@)computed

计算值(computed values)是能够根据现有的状态或其它计算值衍生出的值.若是你想响应式的产生一个能够被其它 observer 使用的值,请使用 @computed.计算值在大多数状况下能够被 MobX 优化的,例如:

  • 前一个计算中使用的数据没有更改,计算属性将不会从新运行;
  • 某个其它计算属性或 reaction 未使用该计算属性,也不会从新运行. 在这种状况下,它将被暂停;
  • 一个计算值再也不被观察了,例如使用它的UI不复存在了,MobX 能够自动地将其垃圾回收;

注意:

  • 计算属性是不可枚举的,它们也不能在继承链中被覆盖;
  • 可使用 observe 或 keepAlive 来强制保持计算值老是处于唤醒状态;
  • observable.object 和 extendObservable 都会自动将 getter 属性推导成计算属性;
  • 若是计算值在其计算期间抛出异常,则此异常将捕获并在读取其值时从新抛出. 强烈建议始终抛出“错误”,以便保留原始堆栈跟踪. 抛出异常不会中断跟踪,全部计算值能够从异常中恢复.

计算值的 setter:

  • 不能用来直接改变计算属性的值,可是它们能够用来做“逆向”衍生.就是反向计算;
  • 必须在 getter 以后定义 setter,一些 TypeScript 版本会知道声明了两个具备相同名称的属性;
  • 这是一个自动的动做,只须要直接使用set xx(){};
class Test {
    @observable num = 0;

    @computed get total() {
        return this.num * 10;
    }

    set total(value) {
        this.num = value / 10;
    }
}
//OR
const Test = observable.object({
    num: 0;

    get total() {
        return this.num * 10;
    }

    set total(value) {
        this.num = value / 10;
    }
})

computed(expression,options) 函数用法

某些状况下,你须要传递一个“在box中”的计算值时,它多是有用的.
options:

  • name: 调试名称,用于 spy 或者 MobX 开发者工具;
  • context: 在提供的表达式中使用的 this;
  • set: 要使用的setter函数. 没有 setter 的话没法为计算值分配新值. 若是传递给 computed 的第二个参数是一个函数,那么就把会这个函数做为 setter;
  • equals: 默认值是 comparer.default .它充当比较函数.若是先后值相等,那么观察者就不会从新评估;
  • requiresReaction: 对于很是昂贵的计算值,推荐设置成 true .若是你尝试读取它的值,但某些观察者没有跟踪该值(在这种状况下,MobX 不会缓存该值),则会致使计算结果丢失,而不是进行昂贵的从新评估;
  • keepAlive: 若是没有任何人观察到,则不要使用此计算值. 请注意,这很容易致使内存泄漏,由于它会致使此计算值使用的每一个 observable ,并将计算值保存在内存中;

MobX 提供了三个内置 comparer (比较器) :

  • comparer.identity: 使用恒等 (===) 运算符来断定两个值是否相同;
  • comparer.default: 等同于 comparer.identity,但还认为 NaN 等于 NaN ;
  • comparer.structural: 执行深层结构比较以肯定两个值是否相同;
const box = observable('box'),
  upperCaseName = computed(() => name.get().toUpperCase()),
  disposer = upperCaseName.observe(change => console.log(change.newValue));
box.set('Dave');

Autorun(expression,options)

建立一个响应式函数,而该函数自己永远不会有观察者,调用后将接收一个参数,即当前 reaction(autorun),可用于在执行期间清理 autorun. 当使用 autorun 时,所提供的函数老是当即被触发一次,而后每次它的依赖关系改变时会再次被触发,相比之下computed(function)建立的函数只有当它有本身的观察者时才会从新计算,不然它的值会被认为是不相关的;
options:

  • delay: 可用于对效果函数进行去抖动的数字(以毫秒为单位).若是是 0(默认值) 的话,那么不会进行去抖.
  • name: 调试名称,用于 spy 或者 MobX 开发者工具;
  • onError: 用来处理 reaction 的错误,而不是传播它们;
  • scheduler: 设置自定义调度器以决定如何调度 autorun 函数的从新运行;
var numbers = observable([1, 2, 3]);
var sum = computed(() => numbers.reduce((a, b) => a + b, 0));

var disposer = autorun(() => console.log(sum.get()));
// 输出 '6'
numbers.push(4);
// 输出 '10'

disposer();
//清理 autorun
numbers.push(5);
// 不会再输出任何值.`sum` 不会再从新计算.

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

when 观察并运行给定的 predicate,直到返回true. 一旦返回 true,给定的 effect 就会被执行,而后 autorunner(自动运行程序) 会被清理. 该函数返回一个清理器以提早取消自动运行程序.

class MyResource {
    constructor() {
        when(
            // 一旦...
            () => !this.isVisible,
            // ... 而后
            () => this.dispose()
        );
    }

    @computed get isVisible() {
        // 标识此项是否可见
    }

    dispose() {
        // 清理
    }
}

when-promise

若是没提供 effect 函数,when 会返回一个 Promise .它与 async / await 能够完美结合.

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

reaction(() => data, (data, reaction) => { sideEffect }, options?)

第一个数据函数是用来追踪并返回数据做为第二个做用函数的入参. 不一样于 autorun 的是当建立时函数不会直接运行,只有在数据表达式首次返回一个新值后才会运行. 在执行做用函数时访问的任何 observable 都不会被追踪.
reaction 返回一个清理函数,接收两个参数,即当前的 reaction,能够用来在执行期间清理 reaction .

options:

  • fireImmediately: 布尔值,用来标识效果函数是否在数据函数第一次运行后当即触发.默认值是 false,若是一个布尔值做为传给 reaction 的第三个参数,那么它会被解释为 fireImmediately 选项;
  • delay: 可用于对效果函数进行去抖动的数字(以毫秒为单位).若是是 0(默认值) 的话,那么不会进行去抖;
  • equals: 默认值是 comparer.default .它充当比较函数.若是先后值相等,那么观察者就不会从新评估;
  • name: 调试名称,用于 spy 或者 MobX 开发者工具;
  • onError: 用来处理 reaction 的错误,而不是传播它们;
  • scheduler: 设置自定义调度器以决定如何调度 autorun 函数的从新运行:
const todos = observable([
    {
        title: "test1"
    }, {
        title: "test2"
    }
]);

//会对长度变化做出反应
reaction(() => todos.length, length => console.log("reaction 1:", todos.map(todo => todo.title).join(", ")));
//会对某个 todo 的 title 变化做出反应
reaction(() => todos.map(todo => todo.title), titles => console.log("reaction 2:", titles.join(", ")));

// autorun 对它函数中使用的任何东西做出反应
autorun(() => console.log("autorun:", todos.map(todo => todo.title).join(", ")));
// 输出:
// autorun: test1

action(() => {
    todos.push({title: "test3"});
})()
// 输出:
// autorun: test1, test2, test3
// reaction 2: test1, test2, test3
// reaction 1: test1, test2, test3

action(() => {
    todos[1].title = 'test4';
})()
// 输出:
// autorun: test1, test4, test3
// reaction 2: test1, test4, test3

@observer

observer 函数/装饰器能够用来将 React 组件/无状态函数组件转变成响应式组件. 它用 mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时均可以强制刷新组件.

  • observer 是由单独的 mobx-react 包提供的.确保 observer 是最深处(第一个应用)的装饰器,不然它可能什么都不作;
  • 若是传递给组件的数据是响应式的,observer还能够防止当组件的 props 只是浅改变时的从新渲染,这个行为与 React PureComponent 类似,不一样在于这里的 state 的更改仍然会被处理. 若是一个组件提供了它本身的 shouldComponentUpdate,这个方法会被优先调用;
import {observer} from "mobx-react";
var timerData = observable({
    secondsPassed: 0
});

setInterval(() => {
    timerData.secondsPassed++;
}, 1000);

@observer class Timer extends React.Component {
    render() {
        return (<span>Seconds passed: { this.props.timerData.secondsPassed } </span> )
    }
};
//OR
const Timer = observer(({ timerData }) =>
    <span>Seconds passed: { timerData.secondsPassed } </span>
);

可观察的局部组件状态

@observer class Timer extends React.Component {
    @observable secondsPassed = 0

    componentWillMount() {
        setInterval(() => {
            this.secondsPassed++
        }, 1000)
    }

    render() {
        return (<span>Seconds passed: { this.secondsPassed } </span> )
    }
}

在React组件上引入可观察属性. 这意味着你能够在组件中拥有功能一样强大的本地状态(local state),而不须要经过 React 的冗长和强制性的 setState 机制来管理. 响应式状态会被 render 提取调用,但不会调用其它 React 的生命周期方法,除了 componentWillUpdate 和 componentDidUpdate . 若是你须要用到其余 React 生命周期方法 ,只需使用基于 state 的常规 React API 便可.

inject组件链接stores

const App = () =>
  <Provider colors={colors}>
     <app stuff... />
  </Provider>;

const Button = inject("colors")(observer(({ colors, label }) =>
  <button style={{
      color: colors.foreground
    }}
  >{label}<button>
));

提供生命週期componentWillReact

当组件从新渲染时被触发,这使得它很容易追溯渲染并找到致使渲染的操做(action).

  • 不接收参数;
  • 初始化渲染前不会触发 (使用 componentWillMount 替代);
  • 对于 mobx-react@4+, 当接收新的 props 时并在 setState 调用后会触发此钩子;

action (动做)

action(fn)
action(name, fn)
@action classMethod() {}
@action(name) classMethod () {}
@action boundClassMethod = (args) => { body }
@action(name) boundClassMethod = (args) => { body }
@action.bound classMethod() {}

action能够是任何用来修改状态的东西,只执行查找,过滤器等函数不该该被标记为action,以容许 MobX 跟踪它们的调用.能够有助于更好的组织代码.

action.bound

自动地将动做绑定到目标对象.与 action 不一样的是不须要一个name参数,名称将始终基于动做绑定的属性.
因為箭头函数已是绑定过的而且不能从新绑定,因此不能一块儿使用

class Ticker {
    @observable tick = 0

    @action.bound
    increment() {
        this.tick++ // 'this' 永远都是正确的
    }
}

编写异步 Actions

action 包装/装饰器只会对当前运行的函数做出反应,而不会对当前运行函数所调用的函数(不包含在当前函数以内)做出反应! 这意味着若是 action 中存在 setTimeout、promise 的 then 或 async 语句,而且在回调函数中某些状态改变了,那么这些回调函数也应该包装在 action 中.

错误写法,抛出异常

class TrafficLight {
    @observable status = "yellow" // "red" / "green" / "yellow"

    @action
    fetchProjects() {
        this.status = "yellow"
        toggleLight().then(
            res => {
                this.status = "green"
            },
            err => {
                this.status = "red"
            }
        )
    }
}

//包装修復在action

class TrafficLight {
    @observable status = "yellow" // "red" / "green" / "yellow"

    @action
    handleAjax() {
        this.status = "yellow"
        toggleLight().then(
            this.handleSuc,
            this.handleErr
        )
    }

    @action.bound
    handleSuc(res){
        this.status = "green"
    }

    @action.bound
    handleErr(err){
        this.status = "red"
    }

}

//另外一种内嵌写法

class TrafficLight {
    @observable status = "yellow" // "red" / "green" / "yellow"

    @action
    handleAjax() {
        this.status = "yellow"
        toggleLight().then(
            action('handleSuc',res => {
                this.status = "green"
            }),
            action('handleErr',res => {
                this.status = "red"
            })
        )
    }
}

runInAction(name?, thunk)
runInAction 是个简单的工具函数,它接收代码块并在(异步的)动做中执行.这对于即时建立和执行动做很是有用.

class TrafficLight {
    @observable status = "yellow" // "red" / "green" / "yellow"

    @action
    handleAjax() {
        this.status = "yellow"
        toggleLight().then(
            runInAction(res => {
                this.status = "green"
            }),
            runInAction(res => {
                this.status = "red"
            })
        )
    }
}

async / await
async / await 只是围绕基于 promise 过程的语法糖. 结果是 @action 仅应用于代码块,直到第一个 await . 在每一个 await 以后,一个新的异步函数将启动,因此在每一个 await 以后,状态修改代码应该被包装成动做.

class TrafficLight {
    @observable status = "yellow" // "red" / "green" / "yellow"

    @action
    async handleAjax() {
        this.status = "yellow"
        toggleLight().then(
            try{
                const result = await dosometings();
                runInAction(res => {
                    this.status = result;
                }),
            }catch(err){
                runInAction(res => {
                    this.status = "red";
                })
            }
        )
    }
}

flow内置概念
优势是它在语法上基本与 async / await 是相同的 (只是关键字不一样),而且不须要手动用 @action 来包装异步代码,这样代码更简洁.

class TrafficLight {
    @observable status = "yellow" // "red" / "green" / "yellow"

    @action
    handleAjax = flow(function* () {
        this.status = "yellow"
        toggleLight().then(
            try{
                const result = yield dosometings();
                this.status = result;
            }catch(err){
                this.status = "red";
            }
        )
    })
}

工具 API

这些 API 都是响应式的,这意味着若是使用 set 进行添加,使用 values 或 keys 进行迭代,即使是新属性的声明均可以被 MobX 检测到.

  • values(thing): 将集合中的全部值做为数组返回;
  • keys(thing): 将集合中的全部键做为数组返回;
  • set(thing, key, value)/set(thing, { key: value }): 使用提供的键值对来更新给定的集合;
  • remove(thing, key): 从集合中移除指定的项.用于数组拼接;
  • has(thing, key): 若是集合中存在指定的 observable 属性就返回 true;
  • get(thing, key): 返回指定键下的子项;
import { get, set, observable, values } from "mobx"

const twitterUrls = observable.object({
    "John": "twitter.com/johnny"
})

autorun(() => {
    console.log(get(twitterUrls, "Sara")) // get 能够追踪还没有存在的属性
})

autorun(() => {
    console.log("All urls: " + values(twitterUrls).join(", "))
})

set(twitterUrls, { "Sara" : "twitter.com/horsejs"})

Mobx工具函数

不想摘抄了,看文档吧...
Mobx工具函数

Mobx技巧与问题

不想摘抄了,看文档吧...
Mobx贴士与技巧

(更多内容请自行查阅,本节到此为止了.)

相关文章
相关标签/搜索