Mobx
是什么
是一个状态管理工具,和 redux 类似
mobx是通过透明的函数式响应编程,会自动的在状态更新之后重新渲染组件
能解决什么问题
区别于 thunk ,mobx 能够直接处理异步
数据是响应式的,可以直接修改数据
适合简单规模不大的应用
怎么用
基本使用
配置环境
安装 mobx
yarn add mobx mobx-react-lite
mobx-react-lite 只支持函数式组件,如果需要在类式组件里面使用,需要使用 mobx-react
核心成员
observable
observable 是一个存储 state 的可追踪字段 (proxy)
action
action方法用于修改 state
computed
computed 用于派生一个 state 的计算属性
工作流程
触发更新 => action 派发更新 => 修改observable 中的 state 状态 => 通过 proxy 通知计算 => 触发 render 函数重新调用更新组件
创建一个 store
mobx中常用的是一个类式写法,也可以理解为是响应式的一种
import { makeObservable,observable,action } from 'mobx'
// makeObservable 可以让数据变成可观察的,接收 1-3 个参数,makeObservable(target,annotation?,option)
// target 是把谁变成相应式(可观察),annotation是注释对象,用于指定哪些属性要变成可观察的,option 是配置对象
class Store{
//在构造函数里面使用 makeObservable
constructor() {
makeObservable(this,{
count:observable//用 observable 将 count 描述为一个可被观察的响应式数据,
updateCount:action// 用 action 将 updateCount 描述为一个可以修改 observable 中数据的方法
})
}
//定义一个初始状态
count = 0
// 定义一个修改方法
updateCount = (count:number ) => {
this.count += count
}
}
export default new Store()
注意:每一个 store 只能初始化一次,所以导出的应该不是 Store 这个类,而是 Store 这个实例,所以需要 new 实例化一下
Store 完成导入后将会是这样
在需要修改状态的地方可以将 Store 导入
import Store from './Store'
console.log(Store);
function App() {
return (
<div className="App">
<div>count : {Store.count}</div>
<div>
<button onClick={()=>Store.updateCount(1)}>+1</button>
<button onClick={()=>Store.updateCount(-1)}>-1</button>
</div>
</div>
);
}
export default App;
但是只是这样,依然不是响应式的,需要定义一个 observer 高阶组件,将组件进行包裹
import Store from './Store'
import { observer } from 'mobx-react'
console.log(Store);
function App() {
return (
<div className="App">
<div>count : {Store.count}</div>
<div>
<button onClick={()=>Store.updateCount(1)}>+1</button>
<button onClick={()=>Store.updateCount(-1)}>-1</button>
</div>
</div>
);
}
export default observer(App);
observer 的本质就是一个高阶组件,使用该组件就可以正常的更新视图了
绑定this指向
因为 mobx 的写法是一个类,但是类中的 this 指向不是特别固定的,上述的写法是一个箭头函数,并且是 Store.方法名 调用,所以 this 的指向为 Store ,但是有一种情况除外
比如有下面这种情况
<button onClick={Store.updateCount}>+1</button>
这种情况下的 this 指向的是 window
但是由于严格模式 ( use strick ) 的限制,任何指向 window 的 this,都会指向 undefind
,所以这种写法会报错,一个错误是因为没有传值,另一个错误是类型错误
所以为了避免这种情况,可以使用 bound 修饰符对 action 进行修饰
constructor() {
makeObservable(this,{
count:observable,
updateCount:action.bound
})
}
一旦制定了 bound ,那么 this 指向将会和 Store 实例绑定
计算属性
计算属性是一个惰性属性,只有当依赖值发生变化的时候才会重新计算
计算属性是一个方法,并且方法前必须使用 get 进行修饰
计算属性必须要在 makeObservable 中进行注释
import { makeObservable,computed,observable } from 'mobx'
constructor() {
makeObserbable(this,{
count:observable,
getTotal:computed
})
}
count = 0
get getTotal() {
console.log('我执行力')
return this.count + 10
}
mobx 中的计算属性和 vue 中的计算属性一样,都会将计算结果进行缓存
在执行了多次计算属性函数后,只执行了一个 log 操作
makeAutoObservable
makeAutoObservable 将会自动的推断属性或者方法成为响应式的属性
-
所有的普通属性将会成为响应式 observable
-
所有的方法将会成为修改状态的 actions
-
所有的 get 修饰的方法将会自动的成为计算属性
语法为:
import { makeAutoObservable } from 'mobx'
class Store {
constructor() {
makeAutoObservable(this,annotation?,options?)
}
count:0
updateCount(count:number) {
this.count += count
}
get getCount () {
retrun this.count * 2
}
}
可以通过第二个参数排除非响应式的属性
import { makeAutoObservable } from 'mobx'
class Store {
constructor() {
makeAutoObservable(this,{
count:false
},{autoBind:true})
}
count:0
updateCount(count:number) {
this.count += count
}
get getCount () {
retrun this.count * 2
}
}
区别于 makeObservable ,annotation 对象中的属性值将是一个布尔值
也可以通过第三个对象中的 autoBind 实现 makeObservable 中的 bound 修饰符一样的 绑定this
的效果
Mobx 监听属性
autorun
autorun 是 Mobx 中的一个监听器,会自动的监听到 store 中的状态的变化。
她接收一个函数作为参数,并且当状态发生改变的时候自动调用该函数
语法为:
import { makeObservable,computed,observable,autorun } from 'mobx'
constructor() {
makeObserbable(this,{
count:observable,
getTotal:computed
})
autorun(()=>{
console.log('状态发生更改了')
})
}
count = 0
get getTotal() {
console.log('我执行力')
return this.count + 10
}
当 mobx 中的状态初始化完成的时候,autorun 就会执行一次
mobx 的原理也是发布订阅模式(observer-dep-wathcer
),会收集所有被引用的数据作为依赖(dep
),然后当状态发生改变的时候会触发所有依赖进行更新(watcher
)
reaction
区别于 autorun,reaction 函数能够更精确的执行,她在初始话的时候不会执行,只有当依赖发生更改的时候才会执行
作为二姐的她接受两个参数,第一个参数是一个函数,用来指定监听状态,第二个参数是一个回调,该回调在监听的状态发生更改后调用,该回调接收两个参数,第一个是新值,第二个是旧值
语法为:
import { makeObservable,computed,observable,reaction } from 'mobx'
constructor() {
makeObserbable(this,{
count:observable,
getTotal:computed
})
reaction(()=>this.count,(newVal,oldVal)=>{
console.log('count更改了' + newVal,oldVal )
})
}
count = 0
get getTotal() {
console.log('我执行力')
return this.count + 10
}
reaction 也可以写在 被使用的组件中,但是由于每次状态更新都会影响组件的重新渲染,所以会出现重复调用的问题
Mobx 异步处理
mobx 中的数据是可以直接使用的,并且直接使用不会发生任何问题,因为只要 Store 中的状态发生了修改,那么就会引起视图的变化
class Store {
constructor() {
makeAutoObservable(this,{},{autoBind:true})
autorun(()=>{
console.log(this.count);
})
reaction(()=>this.count,(newVal,oldVal)=>{
console.log(newVal,oldVal);
})
}
count = 0
get totle() {
console.log('我执行了');
return this.count + 10
}
updateCount(count:number) {
this.count += count
}
likeAxios() {
setTimeout(()=>{
this.count += 1000
},3000)
}
}
虽然状态也可以不用再 action 函数中修改,但是控制台会报警告,并且不安全,所以不推荐使用这种非法操作
解决方法一,使用 configure 改变状态能够在任何地方进行修改
import { makeAutoObservable ,autorun,reaction,configure} from 'mobx'
configure({
enforceActions:'never'
})
class Store {
constructor() {
makeAutoObservable(this,{},{autoBind:true})
autorun(()=>{
console.log(this.count);
})
reaction(()=>this.count,(newVal,oldVal)=>{
console.log(newVal,oldVal);
})
}
count = 0
get totle() {
console.log('我执行了');
return this.count + 10
}
updateCount(count:number) {
this.count += count
}
likeAxios() {
setTimeout(()=>{
this.count += 1000
},3000)
}
}
export default new Store()
解决方法二:异步调用
如果需要异步修改状态,只需要在另一个函数中进行异步操作,然后在异步操作中调用对应的函数即可,所以上面的代码应该写成这样
likeAxios() {
this.count += 1000
}
<button onClick={()=>setTimeout(()=>Store.likeAxios(),3000)}>getRequest</button>
解决办法三:使用 runInAction 函数
runInAction 函数能够直接在异步代码中修改状态,且不会报出警告
import { makeAutoObservable ,autorun,reaction,runInAction} from 'mobx'
class Store {
constructor() {
makeAutoObservable(this,{},{autoBind:true})
autorun(()=>{
console.log(this.count);
})
reaction(()=>this.count,(newVal,oldVal)=>{
console.log(newVal,oldVal);
})
}
count = 0
get totle() {
console.log('我执行了');
return this.count + 10
}
updateCount(count:number) {
this.count += count
}
likeAxios() {
setTimeout(()=>{
runInAction(()=>{
this.count += 1000
})
},3000)
}
}
export default new Store()
使用 runInAction 实际上就是一个语法糖,将本来调用的两个函数,合并为了一个函数
模块化
如果中大型的项目中需要使用多个 mobx 状态,那么可以像 redux 中使用 combineReducer 进行合并模块
可以在 Store 文件夹中定义一个根 Store ,然后使用 useContext + createContext 将其定义为一个自定义 hooks 暴露出去,在需要的组件中进行使用即可
import store1 from './store'
import store2 from './store'
import { useContext,createContext } from 'react'
class RootStore {
store1 = store1
store2 = store2
}
export function useStore () {
return useContext( creactContext(new RootStore))}
如果 createContext 中提供了初始值,那么不需要使用 Provider 引入