Mobx 我的理解

什么是MobX
官方图


image.png

网图


安装

1.安装MobX:yarn add mobx --save

2.安装React绑定库:yarn add mobx-react --save

3.启用装饰器语法(能够使用@标签)

第一步:

yarn add babel-plugin-transform-decorators-legacy babel-preset-react-native-stage-0 --save-dev

第二步:在.babelrc文件中修改为

{
  "presets": ["react-native"],
  "plugins": ["transform-decorators-legacy"]
}
MobX常用标签
@observable : 使用此标签监控要检测的数据; 
@observer: 使用此标签监控当数据变化是要更新的Component(组件类) 
autorun : 当观测到的数据发生变化的时候,如果变化的值处在autorun中,那么autorun就会自动执行。 
@action : 使用此标签监控数据改变的自定义方法(当在需要数据改变的时候执行此自定义方法,那么View层也会跟着自动变化,默认此View层已经使用@observer标签监控) 
useStrict : 使用严格模式 
@computed : 计算值(computed values)是可以根据现有的状态或其它计算值衍生出的值,可以定义在相关数据发生变化时自动更新的值.
Observable
用法
observable(value)
或
@observable classProperty = value

可以理解为被观察者对象,其值可以是JS基本数据类型、引用类型、普通对象、类实例、数组和映射。
注意:如果value是没有原型的对象(例如:{key: 'key', value: 'value'}普通对象是指不是使用构造函数创建出来的对象,而是以 Object 作为其原型,或者根本没有原型。 ),则该对象会被克隆,并且其所有的属性都会被转换为可观察的;如果value是有原型的对象(例如:new MyClass()),MobX 不会将一个有原型的对象自动转换成可观察的,在该类中的构造函数中使用extendObservable或在类定义上使用 @observable / decorate

对observable的变化做出响应

类似于Vue中的计算属性,根据现有被观察的对象计算衍生出新的值。

import {observable, computed} from "mobx"

class Cart {
    @observable price = 0;
    @observable amount = 1;

    constructor(price) {
        this.price = price;
    }

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

若前一个计算中使用的数据没有更改,计算属性将不会重新运行;当其有观察者的时候才会重新计算;如果一个计算属性不在被观察了(使用它的UI被销毁了),Mobx会自动将其回收。

2.autorun

不需要被观察者观察,创建的时候被触发一次,然后每次它的依赖关系改变时会再次被触发 ,常用作打印日志,更新UI等。 autorun 中的值必须要手动清理才行 ,传递给 autorun 的函数在调用后将接收一个参数,即当前 reaction(autorun),可用于在执行期间清理 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'

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

observer 是由单独的 mobx-react 包提供的 ,observer 函数/装饰器可以用来将 React 组件转变成响应式组件

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

class AppState {

  @observable timer = 0

  // 注意这里不能调用super(props),否则报错
  constructor (props) {
    setInterval(() => {
      this.timerIncreat(this.timer)
    }, 1000)
  }

  @action timerIncreat (time) {
    this.timer = time += 1
  }

  // 重置计数器
  @action resetTimer () {
    this.timer = 0
  }
}

export default AppState
import React, {Component} from 'react'
import {View, StyleSheet} from 'react-native'
import {Button, Label} from 'teaset'

import AppState from '../mobx/AppState'
import { observer } from 'mobx-react'

// 实例化,也可在导出的时候实例化
const appState = new AppState()

// 这里必须要写,不然监听不到值的变化
@observer
export default class MobxDemo1 extends Component {

  static navigationOptions = ({navigation, screenProps}) => ({
      headerTitle: 'Mobx练习一'
  })

  render () {
    return (
      <View style={styles.container}>
        <Label style={styles.welcome} text="计数器的一个Mobx例子"/>
        <View style={{flexDirection: 'row', justifyContent: 'space-around', marginTop: 40}}>
          <Label style={styles.welcome} text={`当前的值是:${appState.timer}`}/>
          <Button type='primary' size='md' title='重置' onPress={this.onReset}/>
        </View>
      </View>
    )
  }

  onReset () {
    appState.resetTimer()
  }
}

const styles = StyleSheet.create({
  container: {
      flex: 1,
      // justifyContent: 'center',
      backgroundColor: 'white',
      alignItems: 'center'
  },
  welcome: {
      marginTop: 20,
      fontSize: 20, 
  }
})
改变observable的值
  1. action

顾名思义就是动作,所有被观察的对象被修改都应该通过动作函数来进行修改

用法有以下几种:
action(fn)
action(name, fn)
@action classMethod() {}
@action(name) classMethod () {}
@action boundClassMethod = (args) => { body }
@action(name) boundClassMethod = (args) => { body }
@action.bound classMethod() {}

2.异步action
action只能影响正在运行的函数,而无法影响当前函数调用的异步操作 。action 包装/装饰器只会对当前运行的函数作出反应,而不会对当前运行函数所调用的函数(不包含在当前函数之内)作出反应 ,也就是说promise的then或async语句,并且在回调函数中某些状态改变了,这些回调函数也应该包装在action中。
(1)第一种方案,使用action关键字来包装promises的回调函数。
// 第一种写法

class Store {

@observable githubProjects = []
@observable state = "pending" // "pending" / "done" / "error"

@action
fetchProjects() {
    this.githubProjects = []
    this.state = "pending"
    fetchGithubProjectsSomehow().then(
        // 内联创建的动作
        action("fetchSuccess", projects => {
            const filteredProjects = somePreprocessing(projects)
            this.githubProjects = filteredProjects
            this.state = "done"
        }),
        // 内联创建的动作
        action("fetchError", error => {
            this.state = "error"
        })
    )
 }
}

// 第二种写法

 class Store {
     @observable githubProjects = []
     @observable state = "pending" // "pending" / "done" / "error"

     @action
     fetchProjects() {
         this.githubProjects = []
         this.state = "pending"
         fetchGithubProjectsSomehow().then(
             projects => {
                 const filteredProjects = somePreprocessing(projects)
                 // 将修改放入一个异步动作中
                 runInAction(() => {
                     this.githubProjects = filteredProjects
                     this.state = "done"
                  })
             },
             error => {
                 runInAction(() => {
                     this.state = "error"
                 })
             }
         )
     }
 }

第二种方案,用async function来处理业务,那么我们可以使用runInAction这个API来解决之前的问题 。

import {observable, action, useStrict, runInAction} from 'mobx';
useStrict(true);

class Store {
  @observable name = '';
  @action load = async () => {
    const data = await getData();
    // await之后,修改状态需要动作
    runInAction(() => {
      this.name = data.name;
    });
  }
}
  1. flows
    然而,更好的方式是使用 flow 的内置概念。它们使用生成器。一开始可能看起来很不适应,但它的工作原理与 async / await 是一样的。只是使用 function * 来代替 async,使用 yield 代替 await 。 使用 flow 的优点是它在语法上基本与 async / await 是相同的 (只是关键字不同),并且不需要手动用 @action 来包装异步代码,这样代码更简洁。

flow 只能作为函数使用,不能作为装饰器使用。 flow 可以很好的与 MobX 开发者工具集成,所以很容易追踪 async 函数的过程。

mobx.configure({ enforceActions: true })

class Store {
    @observable githubProjects = []
    @observable state = "pending"

    fetchProjects = flow(function * () { // <- 注意*号,这是生成器函数!
        this.githubProjects = []
        this.state = "pending"
        try {
            const projects = yield fetchGithubProjectsSomehow() // 用 yield 代替 await
            const filteredProjects = somePreprocessing(projects)
            // 异步代码块会被自动包装成动作并修改状态
            this.state = "done"
            this.githubProjects = filteredProjects
        } catch (error) {
            this.state = "error"
        }
    })
}
实际中的运用

跨组件交互
在不使用其它框架、类库的情况下,React要实现跨组件交互这一功能相对有些繁琐。通常我们需要在父组件上定义一个state和一个修改该state的函数。然后把state和这个函数分别传到两个子组件里,在逻辑简单,且子组件很少的时候可能还好,但当业务复杂起来后,这么写就非常繁琐,且难以维护。而用Mobx就可以很好地解决这个问题。来看看以下的例子:

class MyState {
  @observable num1 = 0;
  @observable num2 = 100;

  @action addNum1 = () => {
    this.num1 ++;
  };
  @action addNum2 = () => {
    this.num2 ++;
  };
  @computed get total() {
    return this.num1 + this.num2;
  }
}

const newState = new MyState();

const AllNum = observer((props) => <div>num1 + num2 = {props.store.total}</div>);

const Main = observer((props) => (
  <div>
    <p>num1 = {props.store.num1}</p>
    <p>num2 = {props.store.num2}</p>
    <div>
      <button onClick={props.store.addNum1}>num1 + 1</button>
      <button onClick={props.store.addNum2}>num2 + 1</button>
    </div>
  </div>
));

@observer
export default class App extends React.Component {

  render() {
    return (
      <div>
        <Main store={newState} />
        <AllNum store={newState} />
      </div>
    );
  }
}

有两个子组件,Main和AllNum (均采用无状态函数的方式声明的组件)
在MyState中存放了这些组件要用到的所有状态和函数。
之后只要在父组件需要的地方实例化一个MyState对象,需要用到数据的子组件,只需要将这个实例化的对象通过props传下去就好了。

那如果组件树比较深怎么办呢?
我们可以借助React15版本的新特性context来完成。它可以将父组件中的值传递到任意层级深度的子组件中。
总结
Mobx想要入门上手可以说非常简单,只需要记住少量概念并可以完成许多基础业务了。但深入学习下去,也还是要接触许多概念的。例如Modifier、Transation等等。
最后与Redux做一个简单的对比

Mobx写法上更偏向于OOP
对一份数据直接进行修改操作,不需要始终返回一个新的数据
对typescript的支持更好一些
相关的中间件很少,逻辑层业务整合是一个问题

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容