Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mobx初探 #21

Open
purplebamboo opened this issue Jul 11, 2017 · 3 comments
Open

mobx初探 #21

purplebamboo opened this issue Jul 11, 2017 · 3 comments

Comments

@purplebamboo
Copy link
Owner

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下最合适的选择。

相关引用:

@sivkun
Copy link

sivkun commented Jul 26, 2017

可以考虑在readme里搞个目录

@Natumsol
Copy link

Natumsol commented Aug 7, 2017

mark

@Gavinchen92
Copy link

大神是阿里的吗

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants