purplebamboo / blog

purplebamboo的博客

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

mobx初探

purplebamboo opened this issue · comments

mobx初探

mobx是简单、可扩展的状态管理库。
mobx的核心是TFRP,透明的函数响应式编程(transparently applying functional reactive programming)
mobx认为任何源自应用状态的东西都应该自动地获得。

基本实例

我们通过一个简单的计数器看mobx的实例。这边的例子不包含与react的结合。

import { observable, autorun } from 'mobx';
// let { observable, autorun } = mobx;

// 注入观察钩子
let counter = observable({number:0});

// 运行一次,建立依赖
autorun(() => {
    console.log('number:' + counter.number)
});

setTimeout(function(){
	counter.number ++
},100)
// 结果为:
// number:0
// number:1

在线编辑:https://jsfiddle.net/mweststrate/wv3yopo0/

两个概念:

  • observable 用来包装一个属性为 被观察者
  • autorun 用来包装一个方法为 观察者,可以订阅变更

其实粗略的可以想到原理:

  1. observable 针对对象注入getter,setter钩子。
  2. 运行一次autorun 里面的函数,在对象的getter钩子里建立好依赖关系
  3. 做出数据的修改,触发setter钩子,找到对应的依赖autorun 里面的函数执行。然后又拿到新的依赖(也就是重复2)。

注解的概念与用法

mobx一个很大的特色是可以使用es7的注解增强可读性。我们先回顾下javascript的注解使用方式。虽然还没有完全定稿,不过可以使用babel转义使用。

参考: https://aotu.io/notes/2016/10/24/decorator/index.html

类是个语法糖

class Cat {
    say() {
        console.log("meow ~");
    }
}

等价于:

function Cat() {}
Object.defineProperty(Cat.prototype, "say", {
    value: function() { console.log("meow ~"); },
    enumerable: false,
    configurable: true,
    writable: true
});

类注解

function isAnimal(target) {
    target.isAnimal = true;
  	 return target;
}
@isAnimal
class Cat {
    ...
}

console.log(Cat.isAnimal);    // true

等价于:

	
Cat = isAnimal(function Cat() { ... });

属性注解

function readonly(target, name, descriptor) {
    discriptor.writable = false;
    return discriptor;
}
class Cat {
    @readonly
    say() {
        console.log("meow ~");
    }
}
var kitty = new Cat();
kitty.say = function() {
    console.log("woof !");
}
kitty.say()    // meow ~

等价于:

function Cat() { ... }

let descriptor = {
    value: function() {
        console.log("meow ~");
    },
    enumerable: false,
    configurable: true,
    writable: true
};
descriptor = readonly(Cat.prototype, "say", descriptor) || descriptor;
Object.defineProperty(Cat.prototype, "say", descriptor);

所以属性注解拿到的是 prototype,name,descriptor

例子的注解写法

于是我们看下,注解写法的计数器例子

import { observable, autorun,computed } from 'mobx';

class Counter {
  @observable number = 0;
  @computed get msg() {
    return 'number:' + this.number
  }
}

var store = new Counter()

// 运行一次,建立依赖
autorun(() => {
   console.log(store.msg)
});

// 做出改动
setTimeout(function(){
	store.number ++
},100)

多了几个概念:

  1. @observable,是个装饰器内部会帮你处理注入好观察钩子
  2. @computed,是可以根据现有的状态或其它计算值衍生出的值。@computed 的执行,也会进行依赖收集。

与react结合使用

import { observer } from 'react-mobx';
import React, { Component } from 'react';
import { observable, autorun,computed } from 'mobx';

class Counter {
  @observable number = 0;
  @computed get msg() {
    return 'number:' + this.number
  }
}
var store = new Counter()

@observer
class App extends Component {
  render() {
    return (<div>
        { store.msg } <br />
      <button onClick={this.handleInc}> + </button>
      <button onClick={this.handleDec}> - </button>
    </div>);
  }
  handleInc() {
    store.number ++ ;
  }
  handleDec() {
    store.number -- ;
  }
}

ReactDOM.render(<App />, document.getElementById('root'));

@observer是一个注解,本质上是用 mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件

mobx的action

mobx之前一个比较大的问题是可以随意修改store,后来引入了 action解决这个问题。

action做了这几个事情:

  • 封装事务(transaction)。因为mobx默认改属性是直接同步的。
  • 是 untrack 的。执行表达式,不会去建立依赖。
  • 使用useStrict,可以强制用action改变数据,避免混乱。

例子改写:

import { observer } from 'react-mobx';
import React, { Component } from 'react';
import { observable, autorun,computed } from 'mobx';

class Counter {
  @observable number = 0;
  @computed get msg() {
    return 'number:' + this.number
  }
  @action increment: () => {
    this.number ++ 
  }
  @action decrement: () => {
    this.number -- 
  }
}
var store = new Counter()

@observer
class App extends Component {
  render() {
    return (<div>
        { store.msg } <br />
      <button onClick={this.handleInc}> + </button>
      <button onClick={this.handleDec}> - </button>
    </div>);
  }
  handleInc() {
    store.increment();
  }
  handleDec() {
    store.decrement();
  }
}

ReactDOM.render(<App />, document.getElementById('root'));

这样就比较安全了。

至此,mobx的整个流程就出来了:

tu

mobx的特点

mobx的哲学:

  • 不应该通过手动订阅来同步状态。这将不可避免地导致超额订阅和遗漏订阅。
  • 只有运行时确定下来的订阅,才是最小的订阅集合。

超额订阅

类似redux这样的粗粒度的订阅很容易出现超额订阅的问题:

  • 非实时计算
view() {
  if (count === 0) {
    return a;
  } else {
    return b;
  }
}

基于 redux 的方案,我们必须同时监听 count, a 和 b 。在 counte === 0 的时候,b 如果修改了,也会触发 view 。而这个时候的 b 其实是无意义的。

  • 粗粒度 subscription
view() {
  todos[0].title
}

基于 redux,我们通常会订阅 todos,这样 todos 的新增、删除都会触发 view 。其实这里真正需要监听的是 todos 第一个元素的 title 属性是否有修改。

运行时依赖

与之对应的mobx的运行时依赖,可以做到最小力度。

import { observable, autorun } from 'mobx';

const counter = observable(0);
const foo = observable(0);
const bar = observable(0);
autorun(() => {
  if (counter.get() === 0) {
    console.log('foo', foo.get());
  } else {
    console.log('bar', bar.get());
  }
});

bar.set(10);    // 不触发 autorun
counter.set(1); // 触发 autorun
foo.set(100);   // 不触发 autorun
bar.set(100);   // 触发 autorun

执行结果:

foo 0
bar 10
bar 100

与vue类比

vue其实跟mobx做的事情类似。

  • 都是通过defineproperty的getter,setter钩子来收集依赖。之后调用render去diff。vue是执行watcher的表达式来建立依赖,mobx是通过autorun,执行一次render收集依赖。
  • mobx的概念会更多一点,有独立的store,有action。vue是自己管理。

与redux的对比

Redux 与 MobX 的不同主要集中于以下几点:

  • Redux 是单一数据源,而 MobX 往往是多个 store。MobX 可以根据应用的 UI、数据或业务逻辑来组织 store,具体如何进行需要你自己进行权衡。
  • Redux store 使用普通的 JavaScript 对象结构,MobX 将常规 JavaScript 对象包裹,赋予 observable 的能力,通过隐式订阅,自动跟踪 observable 的变化。MobX 是观察引用的,在跟踪函数中(例如:computed value、reactions等等),任何被引用的 observable 的属性都会被记录,一旦引用改变,MobX 将作出反应。注意,不在跟踪函数中的属性将不会被跟踪,在异步中访问的属性也不会被跟踪。
  • Redux 的 state 是只读的,只能通过将之前的 state 与触发的 action 结合,产生新的 state,因此是纯净的(pure)。而 MobX 的 state 即可读又可写,action 是非必须的,可以直接赋值改变,因此是不纯净的(Impure)。
  • Redux 需要你去规范化你的 state,Immutable 数据使 Reducer 在更新时需要将状态树的祖先数据进行复制和更新,新的对象会导致与之 connect 的所有 UI 组件都重复渲染。因此Redux state 不建议进行深层嵌套,或者需要我们在组件中用 shouldComponentUpdate 优化。而 MobX 只自动更新你所关心的,不必担心嵌套带来的重渲染问题。
  • 在 Redux 中区分有 smart 组件与 dumb 组件,dumb 负责展示,smart 负责状态更新,数据获取。而在 MobX 中无需区分,都是 smart,当组件自身依赖的 observable 发生变化时,会作出响应。

详细的例子

mobx的工程例子:
https://github.com/gothinkster/realworld

结论

mobx 不需要自己管理订阅,可以像vue那样直接帮你解析出依赖,数据流修改起来很自然。而redux的数据流更清晰,一个完整的数据流闭环规范,小项目使用mobx感觉会像vue那样很简单快速,但是大项目还是像redux那样更清晰。目前mobx的社区也没有redux活跃,缺少一些最佳实践。目前来看redux还是react下最合适的选择。

相关引用:

可以考虑在readme里搞个目录

mark

大神是阿里的吗