快速入门 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-react
(mobx-react-lite
只支持函数组件);
2.2 核心概念:
observable
定义一个可存储 state 的可追踪字段;action
将一个方法标记为修改 state 的 action;computed
标记一个可由 state 派生出新值并且 缓存 其输出的计算属性;- 工作流程:
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
```typescript
import { observer } from 'mobx-react'
import Counter from './store/counter'
function App() {
// ...
}
// observer 是个高阶函数,包裹组件,此时 Counter 内的属性改变时,才会精准更新这个组件
export default observer(App)
2.4 **this**
指向问题:
默认 class
中的方法不会绑定this
,this
指向取决于如何调用。<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
指向。
makeObservable(this, {
num: observable,
setIncrement: action.bound, // 重点,通过 bound 将 setIncrement 方法绑定到实例对象上,不可变。
setDecrement: action.bound,
setReset: action.bound
})
此时在组件中调用:<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 }
**2.6 **`**makeAutoObservable**`
```typescript
constructor() {
makeAutoObservable(this, { 排除不需要被观察的属性、方法 }, { autoBind: true })
}
makeAutoObservable
是加强版的makeObservable
,默认情况下它将推断出所有属性:
推断规则如下:
autorun
函数接收一个函数 A 作为参数,每当该函数中观察的属性值发生改变时,A 就会执行。autorun
函数,创建时,会执行一次;- MobX 会自动收集并订阅所有的可观察属性,一旦发生变化,
autoRun
就会触发。 ```typescript class Counter{}
const counter = new Counter()
autorun(() => { console.log(counter.num) })
**3.2 **`**reaction**`**(方法二)**
- `reaction`类似`autorun`,但可更精细控制要跟踪的可观察对象;
- `reaction`接收两个参数:
- 参数1:data 函数,其返回值将会作为第二个函数输入;
- 参数2:callback;
- `reaction`与`autorun`不同,`reaction`在初始化时不会自动执行。
```typescript
reaction(() => counter.num, (v, oldV) => {
console.log('counter.doubleNum', v, oldV)
})
四: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
**4.2 **`**runInAction**`**处理异步**
- 通过`runInAction`可保证所有异步更新可观察对象的步骤都应标识为 action;
```typescript
import { makeObservable, observable, action, runInAction } from 'mobx'
class Count {
constructor() {
makeObservable(this, {
num: observable,
setIncrement: action.bound,
})
}
num = 0
/*
setIncrement() {
// 此时修改的 num 并不是在 action 包裹的函数,而是在定时器的 fun 中修改的,此时,控制台就会出现警告
setTimeout(() => {
this.num++
}, wait)
}
*/
// 处理异步函数,方式二
setIncrement() {
setTimeout(() => {
runInAction(() => { // 重点,
this.num++
})
}, wait)
}
}
// 如何保证每个 store 只初始化一次?导出之前实例化一次后,再导出,即可满足。
const countStore = new Count()
export default countStore
五:MboX 模块化
5.1 多个 store 的场景
出现多个 Store,通过一个根 Store 统一管理所有的 Store,同时还可以相互调用,最后使用 useContext
自定义 Hook (useStore)统一导出 Store。
类比:redux 只有一个 Store。
import { createContext, useContext } from 'react'
import XX from 'store/xx' // 各个子 store
class RootStore {
constructor() {
this.xx1 = XX1
this.xx2 = XX2
// 也可以在引入各个子类Store 之后,再实例化,也可各个子类之间相互共享、传递数据。
/*
this.xx1 = new XX1()
this.xx2 = new XX2()
*/
}
}
const store = new RootStore()
const context = createContext(store)
export default function useStore () {
return useContext(context)
}
在组件中使用时:const { xx1, xx2 } = useStore()