mobx的基本使用

138 阅读5分钟

一、mobx基本介绍

mobx是一个简单、可扩展状态工具,相比redux,具有以下特点

  • 简洁、无模板代码(redux需要写大量模板代码)
  • 响应式数据,可直接修改(redux需要保证不可变)
  • 可直接处理异步(redux需要中间件处理异步)
  • 适合简单、规模不大的应用(redux约束强,更适合大型多人协作开发)
  • 注:mobx6默认不开启修饰器语法

二、mobx的使用

1、依赖安装

npm i mobx mobx-react

2、核心概念

  • observable定义一个存储state的可追踪字段(Proxy)
  • action将一个方法标记为可以修改state的action
  • computed标记一个可以由state派生出新值并且缓存其输出的计算属性

3、创建store

以一个计数器为例,创建文件store/CounterStore.jsx,新建一个CounterStore类,使用makeObservable方法将类的属性和方法变成响应式,并导出实例

:mobx中的每一个store都应该只初始化一次

import { makeObservable, observable, action } from 'mobx';
class CounterStore {
  constructor() {
    makeObservable(this, {
      count: observable,
      increment: action,
      //decrement:action
      decrement: action.bound,
      reset: action,
    })
  }
  count = 0;
  increment() {
    console.log(this);
    this.count++
  }
  decrement() {
    this.count--
  }
  reset() {
    this.count = 0;
  }
}
export default new CounterStore()

在组件中使用,需要在App.jsx文件中引入store,即可使用其属性方法

import counter from './store/CounterStore';
// observer是一个高阶组件函数,需要包裹一个组件,这样组件才会更新
import { observer } from 'mobx-react';
function App() {
  return (
    <div className="App">
      <h3>计数器案例</h3>
      <div>点击次数:{counter.count}</div>
      <button onClick={() => counter.increment()}>加1</button>
      <button onClick={counter.decrement}>减1</button>
      <button onClick={() => counter.reset()}>重置</button>
    </div>
  );
}

export default observer(App);

4、this指向问题

默认class中的方法不会绑定this,this指向取决于如何调用。CounterStore里面的方法的this没有绑定,因此需要通过箭头函数的形式使用

<button onClick={()=> counter.increment()}>加1</button> //正确
<button onClick={counter.decrement}>减1</button> //报错

要想直接使用,需要在CounterStore里面的makeObservable的使用通过action.bound绑定this的指向

makeObservable(this, {
  count: observable,
  increment: action,
  //decrement:action
  decrement: action.bound,
  reset: action,
})

此时App组件中即可直接使用store的方法

<button onClick={counter.decrement}>减1</button> //正确

5、计算属性

mobx的computed可以用来从其他可观察对象中派生信息,具有以下特点:

  • 采用惰性求值,会缓存其输出,并且只有当其依赖的可观察对象被改变是才会重新计算
  • 其前面必须使用get进行修饰
  • 还需要通过makeObservable方法指定

以double为例

CounterStore.jsx

import { makeObservable, observable, action } from 'mobx';
class CounterStore {
  constructor() {
    makeObservable(this, {
      count: observable,
      increment: action,
      //decrement:action
      decrement: action.bound,
      reset: action,
    })
  }
  count = 0;
  increment() {
    console.log(this);
    this.count++
  }
  decrement() {
    this.count--
  }
  reset() {
    this.count = 0;
  }
  get double() {
    return this.count * 2;
  }
}
export default new CounterStore()

App.jsx

import counter from './store/CounterStore';
// observer是一个高阶组件函数,需要包裹一个组件,这样组件才会更新
import { observer } from 'mobx-react';
function App() {
  return (
    <div className="App">
      <h3>计数器案例</h3>
      <div>点击次数:{counter.count}</div>
      <button onClick={() => counter.increment()}>加1</button>
      <button onClick={counter.decrement}>减1</button>
      <div>double:{counter.double}</div>
      <button onClick={() => counter.reset()}>重置</button>
    </div>
  );
}

export default observer(App);

6、makeAutoObservable的使用

makeAutoObservable是加强版的makeObservable,在默认情况下它将推断所有属性。推断规格如下:

  • 所有属性都成为observable
  • 所有方法都成为action
  • 所有的个体都成为computed可以通过第二个参数overrides排除不需要被观察的属性和方法,第三个参数autoBind可以绑定this指向
// 参数1:target,把谁变成响应式(可观察)
// 参数2:排除属性和方法
// 参数3:指定自动绑定this
constructor() {
  constructor() {
    makeAutoObservable(this, {}, { autoBind: true })
  }
}

举例

CounterStore.jsx

import { makeAutoObservable } from 'mobx';
class CounterStore {
  count = 1
  constructor(){
    makeAutoObservable(this, {}, { autoBind: true });
  }
  increment() {
    console.log(this);
    this.count++
  }
  decrement() {
    this.count--
  }
  reset() {
    this.count = 0;
  }
  get double() {
    return this.count * 2;
  }
}
export default new CounterStore()

三、mobx监听属性

mobx中有两个监听方法autorun和reaction,其中:

1、autorun

  • autorun函数接受一个函数作为参数,在创建以及每当该函数所观察的值发生变化时,它都应该运行;mobx会自动收集并订阅所有可观察属性,一旦有改变发生,autorun将会再次触发
autorun(() => {
  console.log("----------"+counter.count);
})

2、reaction

  • reaction类似autorun,但在初始化时不会自动运行,且可以让你更加精细地控制要跟踪的可观察对象,其接受两个函数作为参数,参数1为data函数,其返回值将会作为第二个函数输入,参数2为回调函数
reaction(() => counter.count, (newValue, oldValue) => {
  console.log('counter.count变化了', newValue, oldValue);
})

举例,在App.jsx中,监听count的变化

import { reaction } from 'mobx';
import counter from './store/cnt';
// observer是一个高阶组件函数,需要包裹一个组件,这样组件才会更新
import { observer } from 'mobx-react';
function App() {

  reaction(() => counter.count, (newCount, oldCount) => {
    console.log('count', newCount, oldCount);
  })

  return (
    <div className="App">
      <h3>计数器案例</h3>
      <div>点击次数:{counter.count}</div>
      <button onClick={() => counter.increment()}>加1</button>
      <button onClick={counter.decrement}>减1</button>
      <div>{counter.double}</div>
      <button onClick={() => counter.reset()}>重置</button>
    </div>
  );
}

export default observer(App);

四、异步处理

1、mobx中如何处理异步

异步进程在mobx中不需要任何特殊处理,因为不论是何时引发的所有reaction都将会自动更新,这是因为可观察对象是可变的,在action执行过程中保持对它们的引用一般是安全的。如果可观察对象的修改不是在action函数中,控制台会报警告,这是可以通过runInAction保证所有异步更新可观察对象步骤都标识为action

举例:异步加1

CounterStore.jsx

import { configure, makeAutoObservable } from 'mobx';

configure({
  enforceActions: 'never'
})

class CounterStore {
  count = 1
  constructor(){
    makeAutoObservable(this, {}, { autoBind: true });
  }
  increment() {
    this.count++
  }
  incrementAsync() {
    setTimeout(() => {
      this.count++;
    }, 1000)
  }
  decrement() {
    this.count--
  }
  reset() {
    this.count = 0;
  }
  get double() {
    return this.count * 2;
  }
}
export default new CounterStore()

App.jsx

<button onClick={counter.incrementAsync}>加1异步</button>

2、runInAction的使用

通过runInAction保证所有异步更新可观察对象步骤都标识为action

increment(){
  setTimeout(() => {
    runInAction(( => {
      this.count++;
    })
  }, 1000);
}

五、模块化

1、多个store的场景

  • 项目规模变大之后,不能将所有的状态和方法都放到一个Store中
  • 我们可以根据业务模块定义多个Store
  • 通过一个根Store统一管理所有的Store

2、实现步骤

  • 拆分Counter和Cart两个Store,每个Store都可以有自己的state/action/computed
  • 在store/index.js中导入所有的Store,组合成一个store
  • 使用useContext机制,自定义usestore hook,统一导出Store

(1)新建store/index.js文件,导入所有store,使用useContext机制,自定义useStore hook,统一导出store

import { createContext, useContext } from 'react';
import cart from './Cart';
import counter from './Counter';
class RootStore {
  cart = cart
  counter = counter
}
const store = new RootStore()
const context = createContext(store)
export default function useStore() {
  return useContext(context)
}

(2)在App.js中统一导入,解构得到相应store

import useStore from './store/index';
function App() {
  const { counter, cart } = useStore()
  return (
    <div className="App">
      <h3>计数器案例</h3>
      <div>点击次数:{counter.count}</div>
      <button onClick={() => counter.increment()}>加1</button>
      <button onClick={counter.incrementAsync}>加1异步</button>
      <button onClick={counter.decrement}>减1</button>
      <div>double:{counter.double}</div>
      <button onClick={() => counter.reset()}>重置</button>
      <div>{cart.list}</div>
    </div>
  );
}