MobX 官网

快速入门 MboX

一:MboX 基本概念

介绍:

  • 简单、可扩展( ?)的状态管理工具;
  • 运用透明式的 函数式响应编程 ;

mobx 与 redux 对比:

redux mobx
严格的工作流程,冗余的模板代码 代码简洁
保证数据不可变 数据响应式,可直接修改数据(Proxy)
中间件处理异步 可直接处理异步
redux 约束强,适合大型多人协作开发 适合简单项目

mobx 版本说明:

  • V4 可运行在任何支持 ES5 语法的 浏览器(Object.defineProperty);
  • V5 可运行在 ES6 语法的 浏览器(Proxy
  • V4、V5具有相同 API ,可使用 装饰器 语法;
  • V6 最新版本,默认放弃 装饰器语法(可配置修改支持装饰器语法);

    二:MboX 基本使用

    2.1 环境配置:

  • 使用create-react-app初始化项目,npx create-react-app project-name --template typescript

  • 安装mobx/mobx-reactmobx-react-lite只支持函数组件);

2.2 核心概念:

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

生态 - MobX%26MobX-react - 图1
2.3 创建 Store:

  • 新建文件夹 store,创建文件 counter.ts,通过 class 创建一个 Counter 类;
  • 使用makeObservable将类的 属性、方法 变为响应式;
  • 导出Counter类;
  • 注意:mobx 中的每一个 store 都只初始化一次(mobx 中可有多个 store。如何保证每个 store 只初始化一次?)。 ```typescript import { makeObservable, observable, action } from ‘mobx’

class Count { constructor() { / 参数1:target 指定对象变成可观察的; 参数2:指定哪些 属性、方法变为可观察的; / makeObservable(this, { num: observable, setIncrement: action.bound, setDecrement: action.bound, setReset: action.bound }) } num = 0 setIncrement() { this.num++ } setDecrement() { this.num— } setReset() { this.num = 0 }

// 计算属性 get doubleNum() { console.log(‘执行几次。。。。’); return this.num * 2 } }

// 如何保证每个 store 只初始化一次?导出之前实例化一次后,再导出,即可满足。 const countStore = new Count()

export default countStore

  1. ```typescript
  2. import { observer } from 'mobx-react'
  3. import Counter from './store/counter'
  4. function App() {
  5. // ...
  6. }
  7. // observer 是个高阶函数,包裹组件,此时 Counter 内的属性改变时,才会精准更新这个组件
  8. export default observer(App)

2.4 **this**指向问题:
默认 class中的方法不会绑定thisthis指向取决于如何调用。
<button onClick={() => countStore.setIncrement()}>+</button>
此时setIncrement方法在 箭头函数 中,被countStore调用,此时setIncrement方法中的this指向countStore对象,指向没问题。

如改为如下:
<button onClick={countStore.setIncrement}>+</button>
此时是将setIncrement方法赋值给 onClick 事件,当onClick 调用setIncrement方法时,此方法中的this指向undefined
如何解决:在使用makeObservable时,可通过action.bound绑定this指向。

  1. makeObservable(this, {
  2. num: observable,
  3. setIncrement: action.bound, // 重点,通过 bound 将 setIncrement 方法绑定到实例对象上,不可变。
  4. setDecrement: action.bound,
  5. setReset: action.bound
  6. })

此时在组件中调用:<button onClick={countStore.setIncrement}>+</button>就没有问题。

2.5 计算属性:

  • computed能从可观察对象中派生出信息;
  • 惰性求值,会缓存其输出;
  • 计算属性在类中是一个方法,且方法前面必须使用get修饰;
  • 也可在makeObservale中指定方法是计算属性。 ```typescript // 法一: makeObservable(this, { num: observable, setIncrement: action.bound, // 重点,通过 bound 将 setIncrement 方法绑定到实例对象上,不可变。 setDecrement: action.bound, setReset: action.bound, double: computed })

// 法二,类比 Vue 中的计算属性 get double() { return this.a + this.b }

  1. **2.6 **`**makeAutoObservable**`
  2. ```typescript
  3. constructor() {
  4. makeAutoObservable(this, { 排除不需要被观察的属性、方法 }, { autoBind: true })
  5. }

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

  1. 所有属性变为 observable;
  2. 所有方法变为 action;
  3. 所有 get 修饰的方法变为 computed;

    三:MboX 监听属性

    3.1 **autorun**(方法一)
  • autorun函数接收一个函数 A 作为参数,每当该函数中观察的属性值发生改变时,A 就会执行。
  • autorun函数,创建时,会执行一次;
  • MobX 会自动收集并订阅所有的可观察属性,一旦发生变化,autoRun就会触发。 ```typescript class Counter{}

const counter = new Counter()

autorun(() => { console.log(counter.num) })

  1. **3.2 **`**reaction**`**(方法二)**
  2. - `reaction`类似`autorun`,但可更精细控制要跟踪的可观察对象;
  3. - `reaction`接收两个参数:
  4. - 参数1data 函数,其返回值将会作为第二个函数输入;
  5. - 参数2callback
  6. - `reaction``autorun`不同,`reaction`在初始化时不会自动执行。
  7. ```typescript
  8. reaction(() => counter.num, (v, oldV) => {
  9. console.log('counter.doubleNum', v, oldV)
  10. })

四:MboX 处理异步

4.1 MobX 如何处理异步

  • 异步进程在 mobx 中不需要特殊处理,因为不论何时引发的所有 reactions 都将会自动更新;
  • 可观察对象是可变的,因此在 action 中保持对它们的引用一般是安全的;
  • 若可观察对象的修改不在 action 函数中,控制台会报错(报错可关闭,但是不推荐)。
    • 可通过从import { configure } from 'mobx',然后配置:configure({ enforceActions: 'never' }),此时enforceActions: 'observed' | 'never'
    • observed:可观察状态必须在过 action 修饰过的函数中来修改;
    • never:可观察状态可在任何地方修改(不推荐)。 ```typescript import { makeObservable, observable, action } from ‘mobx’

class Count { constructor() { makeObservable(this, { num: observable, setIncrement: action.bound, }) } num = 0

/ setIncrement() { // 此时修改的 num 并不是在 action 包裹的函数,而是在定时器的 fun 中修改的,此时,控制台就会出现警告 setTimeout(() => { this.num++ }, wait) } / // 处理异步函数,方式一,如下两个方法配合使用 setIncrement() { this.num++ } // 重点 setIncrementAsync() { setTimeout(this.setIncrement, wait) } }

// 如何保证每个 store 只初始化一次?导出之前实例化一次后,再导出,即可满足。 const countStore = new Count()

export default countStore

  1. **4.2 **`**runInAction**`**处理异步**
  2. - 通过`runInAction`可保证所有异步更新可观察对象的步骤都应标识为 action
  3. ```typescript
  4. import { makeObservable, observable, action, runInAction } from 'mobx'
  5. class Count {
  6. constructor() {
  7. makeObservable(this, {
  8. num: observable,
  9. setIncrement: action.bound,
  10. })
  11. }
  12. num = 0
  13. /*
  14. setIncrement() {
  15. // 此时修改的 num 并不是在 action 包裹的函数,而是在定时器的 fun 中修改的,此时,控制台就会出现警告
  16. setTimeout(() => {
  17. this.num++
  18. }, wait)
  19. }
  20. */
  21. // 处理异步函数,方式二
  22. setIncrement() {
  23. setTimeout(() => {
  24. runInAction(() => { // 重点,
  25. this.num++
  26. })
  27. }, wait)
  28. }
  29. }
  30. // 如何保证每个 store 只初始化一次?导出之前实例化一次后,再导出,即可满足。
  31. const countStore = new Count()
  32. export default countStore

五:MboX 模块化

5.1 多个 store 的场景
出现多个 Store,通过一个根 Store 统一管理所有的 Store,同时还可以相互调用,最后使用 useContext自定义 Hook (useStore)统一导出 Store。
类比:redux 只有一个 Store。

  1. import { createContext, useContext } from 'react'
  2. import XX from 'store/xx' // 各个子 store
  3. class RootStore {
  4. constructor() {
  5. this.xx1 = XX1
  6. this.xx2 = XX2
  7. // 也可以在引入各个子类Store 之后,再实例化,也可各个子类之间相互共享、传递数据。
  8. /*
  9. this.xx1 = new XX1()
  10. this.xx2 = new XX2()
  11. */
  12. }
  13. }
  14. const store = new RootStore()
  15. const context = createContext(store)
  16. export default function useStore () {
  17. return useContext(context)
  18. }

在组件中使用时:const { xx1, xx2 } = useStore()