使用Mobx管理 React 应用状态

1、MobX 介绍 

 

MobX 是一个简单、可伸缩的响应式状态管理库。通过 MobX 你可以用最直观的方式修改状态,其他的一切 MobX 都会为你处理好(如自动更新UI),并且具有非常高的性能。当状态改变时,所有应用到状态的地方都会自动更新。 

 

1.1 React和Mobx关系 

 

React 通过提供机制把应用状态转换为可渲染组件树并对其进行渲染。而MobX提供机制来存储和更新应用状态供 React 使用。 

 

1.2 核心概念 

 

  • State:驱动应用的数据
  • Computed values:计算值。如果你想创建一个基于当前状态的值时,请使用 computed
  • Reactions:反应,当状态改变时自动发生
  • Actions:动作,用于改变 State
  • 依赖收集(autoRun):MobX 中的数据以来基于观察者模式,通过 autoRun 方法添加观察者 

 

1.3 工作流程 

 

使用Mobx管理 React 应用状态

state 的更新过程是同步执行的,也就是说,action 更改 state 后,新的 state 可以立即被获取。而 computed value 采用的是延迟更新,只有当它被使用的时候才会重新计算值,当使用 computed value 的组件被卸载时,它也会被自动回收。 

 

⚠️注意:

MobX 中提到的 state 指的是可以观测的 state,因为对于不可观测的 state ,他们的修改并不会自动产生影响,对 MobX 的数据流来说是没有意义的。 

 

2.MobX 核心 API 介绍 

 

2.1. 设置可观察数据 observable 

 

observable 是一种让数据的变化可以被观察的方法,底层是通过把该属性转化成 getter / setter 来实现的。 

 

observable 值可以是 JS原始数据类型、引用类型、普通对象、类实例、数组和映射。 

 

observable使用 

 

对于JS原始类型(Number/String/Boolean), 使用observable.box()方法设置: 

// 设置原始值
const num = observable.box(1)
const string = observable.box('YY')
const bool = observable.box(true)
const symble = observable.box(Symbol(1))
const notnull = observable.box(null)
const undefin = observable.box(undefined)

// 获取原始值 get()
console.log(num.get(),string.get(),bool.get(),symble.get(),notnull.get(),undefin.get()) 

 

对于数组、对象类型,使用 observable() 方法设置: 

const list = observable([1, 2, 3]);
list[2] = 3;
list.push(5) // 可以调用数组方法
console.log(list[0], list[1], list[2], list[3]) // 1 2 3 5
const obj = observable({a: '11', b: '22'})
obj.a = "33";
console.log(obj.a, obj.b) 

 

@observable 使用 

 

装饰器可以在 ES7 或者 TypeScript 类属性中属性使用,将其转换成可观察的。 @observable 可以在实例字段和属性 getter 上使用。 对于对象的哪部分需要成为可观察的,@observable 提供了细粒度的控制。 

 

MobX 也提供使用装饰器 @observable 来将其转换成可观察的,可以使用在实例的字段和属性上。 

 

class store {
  @observable arr = [];
  @observable obj = {};
  @observable map = new Map();
  @observable str = '';
  @observable num = 1;
  @observable bool = false;
  @observable symble = Symble();
  @observable set = new Set();
}

let leo = new store()
  
console.log(leo.num) 

 

其实就是装饰器 @observable 更进一步封装了 observable.box()。 

 

2.2 响应可观察数据的变化 

 

2.2.1 (@)computed 

 

计算值(computed values)是可以根据现有的状态或其它计算值进行组合计算的值。可以使实际可修改的状态尽可能的小。 

 

使用方式 1

class Goods {
  @observable price = 1;
  @observable amount = 2;
  constructor(price = 1) {
    this.price = price;
  }
  @computed get total() {
     return this.price * this.amount;
  }
}

let m = new Goods()
console.log(m.total) // 2
m.price = 10;
console.log(m.total) // 20 

 

使用方式2:使用 decorate

 

class Goods {
  price = 0;
  amount = 2;
  constructor(price = 1) {
    this.price = price;
  }
  get total() {
     return this.price * this.amount;
  }
}
decorate(Goods, {
  price: observable,
  amount: observable,
  total: computed
}) 

 

使用方式3:使用 observable.object 创建 

observable.object 和 extendObservable 都会自动将 getter 属性推导成计算属性 

 

import {observable} from "mobx";
const Goods = observable.object({
  price: 0,
  amount: 1,
  get total() {
    return this.price * this.amount
  }
}) 

 

2.2.2 autorun 

 

autorun 直译就是自动运行的意思,第一次autorun的函数会直接执行,以后只要autorun里面依赖的observable数据改变,autorun都会再次执行 

 

import { observable, autorun } from 'mobx'
class Store {
    @observable str = 'YY';
    @observable num = 123;
}
let store = new Store()
autorun(() => {
    console.log(`${store.str}--${store.num}`)
})
// YY--123
store.str = 'D'
// D--123

 

autorun 默认会执行一次,以获取哪些可观察数据被引用。

autorun 的作用是在可观察数据被修改之后,自动去执行依赖可观察数据的行为,这个行为一直就是传入 autorun 的函数。 

 

2.2.3 when 

 

接收两个函数参数,第一个函数必须根据可观察数据来返回一个布尔值,当该布尔值为 true 时,才会去执行第二个函数,并且只会执行一次。 

 

import { observable, when } from 'mobx'
class Store {
    @observable str = 'YY';
    @observable num = 123;
    @observable bool = false;
}


let sto = new Store()
when(() => sto.bool, () => {
    console.log('这是true')
})
sto.bool = true
// 这是true

 

当 sto.bool 设置成 true 以后,when 的第二个方法便执行了。 

 

2.2.4 reaction 

 

接收两个函数参数,第一个函数引用可观察数据,并返回一个可观察数据,作为第二个函数的参数。 

 

class Store {
  @observable str = 'YY';
  @observable num = 123;
  @observable bool = false;
}
let sto = new Store()
reaction(() => [sto.str, sto.num], arr => {
  console.log(arr)
})
sto.str = 'DT'
sto.num = 122
// [DT, 123]
// [DT, 122] 

 

2.2.5 小结 

 

  1. computed 可以将多个可观察数据组合成一个可观察数据;
  2. autorun 可以自动追踪所引用的可观察数据,并在数据发生变化时自动触发;
  3. when 可以设置自动触发变化的时机,是 autorun 的一个变种情况;
  4. reaction 可以通过分离可观察数据声明,以副作用的方式对 autorun 做出改进; 

 

 

3. 修改可观察数据 

 

action 是修改任何状态的行为,使用 action 的好处是能将多次修改可观察状态合并成一次,从而减少触发 autorun 或者 reaction 的次数。 

 

import { observable, computed, reaction, action} from 'mobx'
class Store {
  @observable string = 'DD';
  @observable number = 123;
  @action bar(){
    this.string = 'YY'
    this.number = 100
  }
}
let store = new Store()

reaction(() => [store.string, store.number], arr => {
    console.log(arr)
})

store.bar() // ["YY", 100] 

 

4.Mobx-React实现ToDoList案例 

 

4.1 初始化 React 项目 

 

用create-react-app脚手架创建一个react项目

然后安装依赖 ,配置一下.babelrc或babel.config.js

yarn add mobx mobx-react

 

4.2 项目的目录层次结构 

 

使用Mobx管理 React 应用状态

 

入口index.js 

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root')); 

 

App.js 

 

Provider组件,在react中,mobx-react提供了 Provider 组件用来包裹最外层组件节点,并且传入 store(通过)context 传递给后代组件 

import React from 'react'
import { Provider } from 'mobx-react'
import store from './store'
import Home from './pages/home'


function App() {
  return (
    <div>
        <Provider store={store}>
            <Home/>
        </Provider>
    </div>
  );
}


export default App;

 

创建Store 类用于存储数据。

 

store/index.js实现代码如下 

 

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


class AppStore {
    @observable time = '2020'
    @observable num = 1
    @observable todos = []
    @computed get desc() {
        return `${this.time} 还有 ${this.todos.length} 条任务待完成`
    }
    @action addTodo(todo) {
        this.todos.push(todo)
    }
    @action deleteTodo() {
        this.todos.pop()
    }
    @action resetTodo() {
        this.todos = []
    }
    @action getNow() {
        this.time = moment().format('YYYY-MM-DD HH:mm:ss')
    }


    @action async result () {
        let num = await this.fetch();
    }


    fetch(){
        return new Promise(resolve => {
            setTimeout(() => {
                resolve(199)
            },1000)
        })
    }
       
}



const store = new AppStore()


setInterval(()=>{
    store.getNow()
}, 1000)


export default store

 

创建<Home> 组件

/pages/home实现代码如下 

 

@inject 是为了向当前被装饰的组件 注入 store 这个props。当然 store 这个 prop 其实是由 Provider 提供的 

import React from 'react'
import {
    inject,
    observer
} from 'mobx-react';


@inject ('store') @observer
export default class Home extends React.Component {
    constructor(props) {
        super(props)
        this.state = {}
    }


    addTodoData = () => {
        let { store } = this.props;
        store.addTodo('一条新任务')
    }


    deleteTodoData =() => {
        let { store } = this.props;
        store.deleteTodo()
    }


    resetTodoData = () => {
        let { store } = this.props;
        store.resetTodo()
    }
    render() {
        let { store } = this.props
        return(
            <div className='home'>
                <h1>在React中使用mobx</h1>
                <div>{store.desc}</div>
                <button onClick={this.addTodoData}>添加任务</button>
                <button onClick={this.deleteTodoData}>删除任务</button>
                <button onClick={this.resetTodoData}>任务重置</button>
                {
                    store.todos.map((item,index,arr) => {
                        return(
                            <div key={index}>{item}</div>
                        )
                    })
                }
            </div>
        )
    }
}


 

效果图 

使用Mobx管理 React 应用状态

 

5.小结

 

MobX 的设计哲学是“可从应用状态中派生的任何内容都应当自动的被派生”,后半句有两个关键字:自动派生。心中秉持这一设计哲学,再来看 MobX 的派生状态模型就比较清晰了,Computed values 和 Reactions 都可以视作是从 State 中派生出的,State 变化时触发 Computed values 的重新计算和 Reations 的重新运行。为了让派生能自动的进行,MobX 通过Object.defineProperyProxy方式拦截对象的读写操作,从而允许用户以自然的方式来修改状态,MobX 负责更新派生的内容。

MobX 提供了几个核心 API 来帮助定义状态(observable)、响应状态(autorun, computed)和修改状态(action),通过这些 API 可以让程序立即具备响应式能力。MobX 可以单独使用,也可以与任何流行的 UI 框架一起使用,不过 MobX 最常见的是与 React 一起使用,mobx-react 是流行的 MobX 和 React 的绑定实现库。

 

参考

《MobX 官方文档》