当项目越发复杂时,我们发现仅仅是提升状态已经无法适应如此复杂的状态管理了,程序状态变得比较难同步,这意味着我们需要更好的状态管理方式,于是就引入了状态管理库,如 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 配置环境
- 使用 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 核心概念
-
observable 定义一个存储 state 的可追踪字段(Proxy)
-
action 将一个方法标记为可以修改 state 的 action
-
computed 标记一个可以由 state 派生出新值并且缓存其输出的计算属性
-
工作流程
2.3 创建 store
- 新建文件 store/Counter.ts, 通过 class 创建一个 Counter 类
- 使用 makeObservable 将类的方法 和属性变成响应式的
- 导出 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 指向问题
- 默认 class 中的方法不会绑定 this,this 指向取决于如何调用
// 正确
<button onClick={() => counter.increment()}>加1</button>
// 错误
<button onClick={counter.increment}>加1</button>
- 在使用 makeObservable 的时候可以通过 action.bound 绑定 this 的指向
makeObservable(this, {
count: observable,
increment: action.bound,
reset: action.bound,
})
此时组件中即可直接使用 store 的方法
<button onClick={counter.increment}>加1</button>
2.6 计算属性
- computed 可以用来从其他可观察对象中派生信息
- 计算值采用惰性求值,会缓存其输出,并且只有当其依赖的可观察对象被改变是才会重新计算
- 计算值是一个方法,且方法前面必须使用 get 进行修饰
- 计算值需要通过 makeObservable 方法指定
...
makeObservable(this, {
count: observable,
increment: action.bound,
reset: action.bound,
double: computed,
})
...
get double(){
return this.count * 2
}
2.7 makeAutoObservable 的使用
makeAutoObservable 是加强版的 makeObservable,在默认情况下它将推断所有属性。
推断规格如下:
- 所有属性都成为 observable
- 所有方法都成为 action
- 所有的个体都成为 computed
// 参数1:target,把谁变成响应式(可观察)
// 参数2:排除属性和方法
// 参数3:利用 autoBind 指定自动绑定 this
makeAutoObservable(this, {decrement: true}, {autoBind: true})
Mobx 监听属性
3.1 autorun 的使用
- autorun 函数接受一个函数作为参数,在创建以及每当该函数所观察的值发生变化时,它都应该运行
- 当创建 autorun 时,它会运行一次
- Mobx 会自动收集并订阅所有可观察属性,一旦有改变发生,autorun 将会再次触发
autorun(() => {
console.log('counter', counter.count);
})
3.2 reaction 的使用
- reaction 类似 autorun,但可以让你更加精细地控制要跟踪的可观察对象
- 接受两个函数作为参数
- 参数 1: data 函数,其返回值将会作为第二个函数输入
- 参数 2: 回调函数
- 与 autorun 不同,reaction 在初始化时不会自动运行
reaction(
() => counter.count,
(newValue, oldValue) => {
console.log('counter.count变化了', newValue, oldValue);
}
)
Mobx 处理异步
4.1 Mobx 如何处理异步
- 异步进程在 Mobx 中不需要任何特殊处理,因为不论是何时引发的所有 reaction 都将会自动更新,
- 这是因为可观察对象是可变的,在 action 执行过程中保持对它们的引用一般是安全的
- 如果可观察对象的修改不是在 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
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 的源码,多多了解底层思想,这才是关键~