likes
comments
collection
share

十分钟玩转Mobx(不得不看啊)

作者站长头像
站长
· 阅读数 16

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

mobx的基本概念

1.1 介绍

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

1.2 Redux和Mobx的区别

十分钟玩转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 初始化项目
npx create-react-app my-app --template typescript

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

yarn add Mobx Mobx-react --save

2.2 核心概念

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

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

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

  4. 工作流程

十分钟玩转Mobx(不得不看啊)

2.3 创建 store

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

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

// store/Counter.ts
import {action, makeObservable, observable} from 'Mobx'
class Counter {
  constructor(){
    // 参数1:target,把谁变成响应式(可观察)
    // 参数2:指定哪些属性或者方法变成可观察
    makeObservable(this, {
       count: observable,
       increment: action,
       decrement: action,
       reset: action,
     })
  }
  count = 0
  increment(){
    this.count++
  }
  decrement(){
    this.count--
  }
  reset(){
    this.count = 0
  }
}
const counter = new Counter()
export default counter

2.4 在组件中使用

  • 从 Mobx-react 库中引入 observer 高阶组件函数
  • 使用 observer 高阶组件函数包裹需要使用 store 的组件
  • 引入 store 对象
  • 使用 store 对象中的属性和方法即可
// App.tsx
import counter from './store/Counter';
// observer 是一个高阶组件函数,需要包裹一个组件,这样组件才会更新
import { observer } from 'Mobx-react'

function App() {
  const {cart, counter} = useStore()
  return (
    <div className="App">
      <h3>计数器案例</h3>
      <div>点击次数:{counter.count}</div>
      <button onClick={()c=> ounter.increment()}>加1</button>
      <button onClick={()c=> ounter.decrement()}>减1</button>
      <button onClick={() => counter.reset()}>重置</button>
    </div>
  );
}
export default observer(App);

2.5 处理 this 指向问题

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

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

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

2.6 计算属性

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

2.7 makeAutoObservable 的使用

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

推断规格如下:

  1. 所有属性都成为 observable
  2. 所有方法都成为 action
  3. 所有的个体都成为 computed
  // 参数1:target,把谁变成响应式(可观察)
  // 参数2:排除属性和方法
  // 参数3:利用 autoBind 指定自动绑定 this
  makeAutoObservable(this, {decrement: true}, {autoBind: true})

Mobx 监听属性

3.1 autorun 的使用

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

3.2 reaction 的使用

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

Mobx 处理异步

4.1 Mobx 如何处理异步

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

4.2 runInAction 的使用

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

  // 解决2: 放在 runInAction 中
  incrementAsync(){
    setTimeout(() => {
      runInAction(() => {
        this.count++
      })
    }, 1000)
  }

Mobx 模块化

5.1 多个 store 场景

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

十分钟玩转Mobx(不得不看啊)

5.2 实现步骤

  • 拆分 Counter 和 Cart 两个 store, 每个 store 都可以有自己的 state/action/computed
  • 在 store/index.ts 文件,导入所有 store,组合成一个 store
  • 使用 useContext 机制,自定义 useStore hook,统一导出 store
// store/index.ts
import { useContext, createContext } from 'react'
import cart from './Cart'
import counter from './Counter'

class RootStore {
  cart = cart
  counter = counter
}
const store = new RootStore()

// 创建一个上下文对象,用于跨级组件通讯
// 如果 createContext 提供了默认值,不需要 Provider
const Context = createContext(store)

// 自定义 hooks
export const useStore = () => {
  return useContext(Context)
}
// App.tsx
import {useStore} from './store'
...
    const {cart, counter} = useStore()
...

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