当项目越发复杂时,我们发现仅仅是提升状态已经无法适应如此复杂的状态管理了,程序状态变得比较难同步,这意味着我们需要更好的状态管理方式,于是就引入了状态管理库,如 Redux,Mobx 等。本文以Mobx 为主,再重新回顾一下~

mobx的基本概念

1.1 介绍

  • 简单、可扩展的状态管理工具
  • 通过运用透明的函数式响应编程使状态管瘤变得简单和可拓展

1.2 Redux和Mobx的区别

注意:复杂的应用也不是说不能用,需要合理的理清业务逻辑关系,合理的使用。

1.3 版本说明

  • Mobx 4 可以运行在任何支持 ES5 语法的浏览器(Object.defineProperty)
  • Mobx 5 版本运行在任何支持 ES5 语法的浏览器(Proxy)
  • Mobx 4 和 Mobx 5 具有相同的 api,都需要使用装饰器语法
  • Mobx 6 是目前最新版本,为了与标准 javaScript 的最大兼容,默认情况下放弃了装饰器语法。(本文主要介绍 Mobx6)

Mobx的基本使用

2.1 配置环境

  1. 使用 create-react-app 初始化项目
 1npx create-react-app my-app --template typescript

安装 mobx、mobx-react 或者 Mobx-react-lite【只支持函数组件】

 1yarn add Mobx Mobx-react --save

2.2 核心概念

  1. observable 定义一个存储 state 的可追踪字段(Proxy)

  2. action 将一个方法标记为可以修改 state 的 action

  3. computed 标记一个可以由 state 派生出新值并且缓存其输出的计算属性

  4. 工作流程

2.3 创建 store

  1. 新建文件 store/Counter.ts, 通过 class 创建一个 Counter 类
  2. 使用 makeObservable 将类的方法 和属性变成响应式的
  3. 导出 counter 实例

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

 1
 2import {action, makeObservable, observable} from 'Mobx'
 3class Counter {
 4  constructor(){
 5    
 6    
 7    makeObservable(this, {
 8       count: observable,
 9       increment: action,
10       decrement: action,
11       reset: action,
12     })
13  }
14  count = 0
15  increment(){
16    this.count++
17  }
18  decrement(){
19    this.count--
20  }
21  reset(){
22    this.count = 0
23  }
24}
25const counter = new Counter()
26export default counter

2.4 在组件中使用

  • 从 Mobx-react 库中引入 observer 高阶组件函数
  • 使用 observer 高阶组件函数包裹需要使用 store 的组件
  • 引入 store 对象
  • 使用 store 对象中的属性和方法即可
 1
 2import counter from './store/Counter';
 3
 4import { observer } from 'Mobx-react'
 5
 6function App() {
 7  const {cart, counter} = useStore()
 8  return (
 9    <div className="App">
10      <h3>计数器案例</h3>
11      <div>点击次数:{counter.count}</div>
12      <button onClick={()c=> ounter.increment()}>加1</button>
13      <button onClick={()c=> ounter.decrement()}>减1</button>
14      <button onClick={() => counter.reset()}>重置</button>
15    </div>
16  );
17}
18export default observer(App);

2.5 处理 this 指向问题

  1. 默认 class 中的方法不会绑定 this,this 指向取决于如何调用
 1
 2<button onClick={() => counter.increment()}>加1</button>
 3
 4<button onClick={counter.increment}>加1</button>
  1. 在使用 makeObservable 的时候可以通过 action.bound 绑定 this 的指向
 1  makeObservable(this, {
 2    count: observable,
 3    increment: action.bound,
 4    reset: action.bound,
 5  })

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

 1<button onClick={counter.increment}>加1</button>

2.6 计算属性

  1. computed 可以用来从其他可观察对象中派生信息
  2. 计算值采用惰性求值,会缓存其输出,并且只有当其依赖的可观察对象被改变是才会重新计算
  3. 计算值是一个方法,且方法前面必须使用 get 进行修饰
  4. 计算值需要通过 makeObservable 方法指定
 1...
 2  makeObservable(this, {
 3      count: observable,
 4      increment: action.bound,
 5      reset: action.bound,
 6      double: computed,
 7    })
 8...
 9get double(){
10  return this.count * 2
11}

2.7 makeAutoObservable 的使用

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

推断规格如下:

  1. 所有属性都成为 observable
  2. 所有方法都成为 action
  3. 所有的个体都成为 computed
 1  
 2  
 3  
 4  makeAutoObservable(this, {decrement: true}, {autoBind: true})

Mobx 监听属性

3.1 autorun 的使用

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

3.2 reaction 的使用

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

Mobx 处理异步

4.1 Mobx 如何处理异步

  1. 异步进程在 Mobx 中不需要任何特殊处理,因为不论是何时引发的所有 reaction 都将会自动更新,
  2. 这是因为可观察对象是可变的,在 action 执行过程中保持对它们的引用一般是安全的
  3. 如果可观察对象的修改不是在 action 函数中,控制台会报警告(可以关闭,但是不推荐)
 1
 2import { configure } from 'Mobx'
 3configure({
 4  
 5  
 6  enforceActions: 'never'
 7})
 8  
 9  increment(){
10    this.count++
11  }
12  incrementAsync(){
13    setTimeout(this.increment, 1000)
14  }

4.2 runInAction 的使用

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

 1  
 2  incrementAsync(){
 3    setTimeout(() => {
 4      runInAction(() => {
 5        this.count++
 6      })
 7    }, 1000)
 8  }

Mobx 模块化

5.1 多个 store 场景

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

5.2 实现步骤

  • 拆分 Counter 和 Cart 两个 store, 每个 store 都可以有自己的 state/action/computed
  • 在 store/index.ts 文件,导入所有 store,组合成一个 store
  • 使用 useContext 机制,自定义 useStore hook,统一导出 store
 1
 2import { useContext, createContext } from 'react'
 3import cart from './Cart'
 4import counter from './Counter'
 5
 6class RootStore {
 7  cart = cart
 8  counter = counter
 9}
10const store = new RootStore()
11
12
13
14const Context = createContext(store)
15
16
17export const useStore = () => {
18  return useContext(Context)
19}
20
21import {useStore} from './store'
22...
23    const {cart, counter} = useStore()
24...

各种状态管理熟悉流程后才能更多把握它的一些核心理念,使用起来可能更有心得及感悟。而 Mobx 又更简单化,把大部分东西隐藏起来,如果不去特别研究就不能接触到它的核心/基本思想,会使得我们一直停留在使用层次。研究 Mobx 的源码,多多了解底层思想,这才是关键~

个人笔记记录 2021 ~ 2025