purplebamboo / blog

purplebamboo的博客
453 stars 35 forks source link

mobx初探 #21

Open purplebamboo opened 7 years ago

purplebamboo commented 7 years ago

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/

两个概念:

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

  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做了这几个事情:

例子改写:

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 其实是无意义的。

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做的事情类似。

与redux的对比

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

详细的例子

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

结论

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

相关引用:

sivkun commented 7 years ago

可以考虑在readme里搞个目录

Natumsol commented 7 years ago

mark

Gavinchen92 commented 7 years ago

大神是阿里的吗