mobx
文章目录
1. MobX基本概念
mobx是一个简单、可扩展的状态管理工具,通过运用透明的函数式响应编程使状态管理变得简单。
特点:
- 无模版代码,非常简洁;
- 数据是响应式的,可以直接修改数据(Proxy);
- 可以直接处理异步;
- 适合简单、规模不大的应用。(redux约束强,更适合大型多人协作开发)
版本说明
- Mobx4可以运行在任何支持ES5语法的浏览器;
- Mobx5版本运行在任何支持ES6语法的浏览器;
- Mobx4和Mobx5具有相同的api,都需要使用装饰器语法;
- Mobx6是目前最新的版本,为了兼顾与标准JavaScript的最大兼容性,默认情况下放弃了装饰器语法。
2. MobX基本使用
2.1 安装环境
初始化项目:
npx create-react-app mobx-demo --template typescript
安装库 mobx、mobx-react或者mobx-react-lite(只支持函数组件)
yarn add mobx mobx-react
2.2 mobx基本使用
**observable**定义一个存储state的可追踪字段(Proxy)。
**action**将一个方法标记为可修改state的action。
**computed**标记一个可以由state派生出新值并且缓存其输出的计算属性。
工作流程:
2.3 创建store
- 新建文件store/Counter.ts,通过class创建一个Counter类;
- 使用makeObservable将类的属性和方法变成响应式的;
// 参数1:target把谁变成响应式(可观察)
// 参数2: 指定哪些属性或方法变成可观察
makeObservable(this, {
count: observable, // 属性是observable的
increment: action.bound,
decrement: action.bound,
reset: action, // 如果要改数据,那么这个方法一定要标为action
double: computed
});
- 导出counter实例;
- 注意:mobx中每一个store都只应该初始化一次。
2.4 在组件中使用
- 从mobx-react-lite或mobx-react库中引入observer高阶组件函数;
- 使用observer高阶组件函数包裹需要使用store的组件;
- 引入store对象;
- 使用store对象的属性和方法即可。
2.5 处理this指向
默认class中的方法不会绑定this,this指向取决于如何调用
makeObservable(this, {
count: observable, // 属性是observable的
increment: action,
decrement: action,
reset: action // 如果要改数据,那么这个方法一定要标为action
});
<button onClick={() => counter.increment()}>加1</button> // 正确
<button onClick={counter.increment>加1</button> // 错误
在使用makeObservable时可以通过action.bound绑定this指向。
makeObservable(this, {
count: observable, // 属性是observable的
increment: action.bound,
decrement: action.bound,
reset: action // 如果要改数据,那么这个方法一定要标为action
});
<button onClick={counter.increment}>加1</button>
2.6 计算属性的使用
computed可以用来从其他可观察对象中派生信息。
- 计算值采用惰性求值,会缓存其输出,并且只有当其依赖的可观察对象被改变时才会重新计算。
- 计算属性是一个方法,且方法前面必须使用get进行修饰。
- 计算属性还需要通过makeObservable方法指定。
get double() {
return this.count * 2;
}
2.7 makeAutoObservable的使用
makeAutoObservable就像是加强版的makeObservable。在默认情况下它将推断所有的属性。
// 参数1:target 让哪个对象变成可观察
// 参数2: 排除属性和方法,不让其成为可观察的
// 参数3: 指定自动绑定this
makeAutoObservable(this, {}, { autoBind: true })
推断规则如下:
- 所有的属性都成为observable;
- 所有的方法都成为action;
- 所有的get都成为computed。
通过overrides排除不需要被观察的属性和方法:
constructor() {
makeAutoObservable(this, { decrement: false })
}
通过autoBind可以绑定this指向:
constructor() {
makeAutoObservable(this, { decrement: false }, { autoBind: true })
}
<button onClick={counter.increment}>加1</button>
不写{ autoBind: true }时,button中的点击回调函数应该这样写:
constructor() {
makeAutoObservable(this, { decrement: false })
}
<button onClick={() => counter.increment()}>加1</button>
3. MobX监听属性
3.1 autoRun的使用
autoRun函数接受一个函数作为参数,每当该函数所观察的值发生变化时,它都应该运行。
当你自己创建autoRun时,它也会运行一次。
MobX会自动收集并订阅所有的可观察属性,一旦有改变发生,autoRun将会再次触发。
import { makeAutoObservable, autorun } from 'mobx'
class Counter {
constructor() {
// 参数1:target 让哪个对象变成可观察
// 参数2: 排除属性和方法,不让其成为可观察的
// 参数3: 指定自动绑定this
makeAutoObservable(this, {}, { autoBind: true })
}
count = 0;
increment() {
this.count++;
}
decrement() {
this.count--;
}
reset() {
this.count = 0;
}
get double() {
return this.count * 2;
}
}
const counter = new Counter();
// 只要count发生变化,autorun就会自动执行
// 如果里面有很多个属性,只要其中一个发生变化,autorun里的内容就会全部执行一次
autorun(() => {
console.log('counter.count', counter.count);
})
export default counter; // 导出了一个对象
3.2 reaction的使用
reaction类似于autorun,但可以让你更加精细地控制要跟踪的可观察对象。
它接受两个函数作为参数,
- 参数1:data函数,其返回值将会作为第二个函数输入
- 参数2:回调函数
与autoRun不同,reaction在初始化时不会自动运行
import { reaction } from 'mobx'
reaction(
() => counter.count,
(value, oldValue) => {
console.log('counter.count发生了变化', value, oldValue)
}
)
4. MobX处理异步
4.1 MobX如何处理异步
异步进程在mobx中不需要任何特殊处理,因为不论是何时引发的,所有reactions都将会自动更新。
因为可观察对象是可变的,因此在action执行过程中保持对它们的引用一般是安全的。
如果可观察对象的修改不是在action函数中,控制台会报警告(可以关闭,但是不推荐)。
示例:
想让点击+1的时候,过1秒count再+1
constructor() {
makeAutoObservable(this, { decrement: false })
}
increment() {
setTimeout(() => {
this.count++;
}, 1000)
}
上面代码功能是正常的,但是会报警告:
[MobX] Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed. Tried to modify: Counter@1.count
原因是可观察的值的修改,应该在action函数中(如increment()函数)进行,但是实际上却是在延时函数setTimeout()中进行的。
想要不看到这个警告,有两种方法:
- 关掉警告(不推荐!!)
import { configure } from 'mobx'
configure({
enforceActions: 'never'
})
- 换一种写法,将同步和异步拆开
increment() {
this.count++;
}
incrementAsync() {
setTimeout(this.increment, 1000)
}
<button onClick={() => counter.incrementAsync()}>加1</button>
注意: 修改状态一定要在action函数中进行
4.2 runInAction
通过runInAction可以保证所有异步更新可观察对象的步骤都应该标识为action。
import { runInAction } from 'mobx'
increment() {
setTimeout(() => {
runInAction(() => {
this.count++;
})
}, 1000)
}
5. MobX模块化
5.1 多个store的场景
项目规模变大后,不能将所有的状态和方法都放到一个store中。
我们可以根据业务模块定义多个sore。
import counter from "./store/Counter";
import { observer } from 'mobx-react';
import cart from './store/Cart';
// observer是一个高阶组件函数,需要包裹一个组件,这样这个组件才会更新
function App() {
return (
<div className="App">
<h3>计数器案例</h3>
<div>点击次数:{counter.count}</div>
<div >double: {counter.double}</div>
<button onClick={() => counter.increment()}>加1</button>
<button onClick={() => counter.decrement()}>减1</button>
<button onClick={() => counter.reset()}>重置</button>
<div>{cart.list}</div>
</div>
);
}
export default observer(App);
通过一个根store统一管理所有的store。
5.2 实现步骤
- 拆分Counter和Cart两个Store,每个Store都可以有自己的state、action、computed;
- 在store/index.ts中导入所有的Store,组合成一个Store;
- 使用useContext机制,自定义useStore hook,统一导出Store。
- createContext作用是创建一个上下文对象,用于跨组件通讯
import { createContext, useContext } from 'react'
import cart from './Cart'
import counter from './Counter'
class RootStore {
cart = cart;
counter = counter;
}
const store = new RootStore()
// 创建一个上下文对象,用于跨组件通讯
const Context = createContext(store)
// 自定义hooks
export default function useStore() {
return useContext(Context)
}
调用store
// import counter from "./store/Counter";
// import cart from './store/Cart';
import { observer } from 'mobx-react';
// observer是一个高阶组件函数,需要包裹一个组件,这样这个组件才会更新
import useStore from './store'
function App() {
const { counter, cart } = useStore() // 解构
return (
<div className="App">
<h3>计数器案例</h3>
<div>点击次数:{counter.count}</div>
<div >double: {counter.double}</div>
<button onClick={() => counter.increment()}>加1</button>
<button onClick={() => counter.decrement()}>减1</button>
<button onClick={() => counter.reset()}>重置</button>
<div>{cart.list}</div>
</div>
);
}
export default observer(App);