了解完Recoil后,立刻来学习了mobx,趁热打铁。
如果想要在react中使用mobx,我们需要安装mobx-react
或者mobx-react-lite
。
- 如果只想在函数式组件中使用mobx,那么只需要安装
mobx, mobx-react-lite
。 - 如果想要在类组件或者函数式组件中使用mobx,那么需要安装
mobx, mobx-react
。
mobx
MobX 帮助你以一种简单直观的方式来完成工作,并且mobx中的每一个store都只能被初始化一次。
mobx初体验
// store.js
import { makeObservable, action, observable, computed } from 'mobx'
class Num {
constructor() {
makeObservable(this, {
// 让其成为可响应式的属性
num: observable,
// action: 表示指定该方法是一个action方法,不让控制台报警告
// bound: 表示自动绑定该方法的this
up: action.bound,
down: action.bound,
// computed: 表示当前值是一个计算值。会存在缓存
double: computed
})
}
num = 0
up() {
this.num++
}
down() {
this.num--
}
get double() {
return this.num * 2
}
}
export default new Num()
// 使用store的值
import React from 'react'
import { observer } from 'mobx-react'
import Num from '../store/index'
function Test() {
return (
<div className="App">
<p>{Num.num}</p>
<p>{Num.double}</p>
<button onClick={Num.up}>增加</button>
<button onClick={Num.down}>减少</button>
</div>
)
}
export default observer(Test)
mobx的一些概念
State(状态)
状态 是驱动应用的数据。 就是我们通过mobx管理的数据。
Derivations(衍生)
任何 源自状态并且不会再有任何进一步的相互作用的东西就是衍生。
MobX 区分了两种类型的衍生:
- Computed values(计算值)。它们是永远可以使用纯函数(pure function)从当前可观察状态中衍生出的值。感觉他特别像vuex中的getter。
- Reactions(反应)。Reactions 是当状态改变时需要自动发生的副作用。需要有一个桥梁来连接命令式编程(imperative programming)和响应式编程(reactive programming)。或者说得更明确一些,它们最终都需要实现I / O 操作。
如果你想创建一个基于当前状态的值时,请使用 computed
。
Actions(动作)
动作 是任一一段可以改变状态的代码。用户事件、后端数据推送、预定事件、等等。 就是更新mobx管理的state。
mobx常见api讲解
observer
如果我们想在组件中使用mobx定义的state等,我们就需要使用observer将组件包裹。
注意这个高阶函数是在react-mobx
库中的。
export default observer(Test)
makeObservable
定义store中属性和动作的配置。
makeObservable(this, {
// 让其成为可响应式的属性
num: observable,
// action: 表示指定该方法是一个action方法
// bound: 表示自动绑定该方法的this
up: action.bound,
down: action.bound,
// computed: 表示当前值是一个计算值。会存在缓存
double: computed
})
}
observable
让store中的数据成为可响应式的属性。
action
表示指定该方法是一个action方法。
bound
表示自动绑定该方法的this。省去我们在使用时给该方法绑定this。
computed
表示当前值是一个计算值。会将值进行缓存。当依赖的state发生变化后,自动计算,并重新缓存。
makeAutoObservable
我们知道,在定义完state或者action以后,我们需要配置他们,让其成为一个可响应式的数据或者一个action,这就很麻烦了。
但是这个api可以自动推断我们的state和action,并自动进行配置。
- 所有的属性都成为
observable
。 - 所有的方法都成为
action
。 - 所有的gey都成为
computed
。
并且可以通过后续的参数来排除一些默认的这个配置。
// 第二个参数表示不适用他的默认推导,所以点击减号就会报错
// 第三个参数表示自动绑定this
makeAutoObservable(this, { down: false }, { autoBind: true })
监听属性
autorun
自动收集使用的依赖,然后进行监听。
默认页面加载就会执行一次,当使用的state依赖发生变化,可以执行。类似于vue中的watchEffect。
autorun(() => {
console.log('自动收集依赖,然后执行...', num1.num)
})
reaction
不会立刻执行,当监听的依赖变化时才会执行。类似于vue中的watch。
reaction(
() => num1.num,
() => {
console.log('指定依赖,然后监听执行...', num1.num)
}
)
mobx处理异步更新
异步操作在mobx中不需要任何特殊处理,因为不论是何时引发的所有reactions都将自动更新。
异步直接操作action方法不会有问题,数据一样会被响应。但是控制台会报警告。
// 异步操作
increment() {
setTimeout(() => {
this.num++
})
}
上面这个警告可以通过配置给其关闭。
configure({
enforceActions: 'never'
})
以上这种方式不推荐。不要直接在action函数中异步修改state。
正确解决异步操作
- 通过定义一个额外的函数来充当中介,调用action函数。
// 正确的异步:方式一
incrementAsync() {
setTimeout(this.up, 1000)
}
- 通过runInAction来异步更改state。
// 正确的异步,方式二
incrementAsync() {
setTimeout(() => {
runInAction(() => {
this.num++
})
}, 1000)
}
对于多个store,如何优雅的使用
我们在一个组件中可能会使用多个store中的state。所以,为了避免多次导入不同的文件,我们可以有以下处理方式
- 定义一个统一的store出口文件,将store统一导出。
// store/index.js
import a from './aStore.js'
import b from './bStore.js'
export {
a,
b
}
- 通过react提供的context
import { createContext, useContext } from 'react'
import a from './aStore.js'
import b from './bStore.js'
class RootStore {
a = a
b = b
}
const store = new RootStore()
const context = createContext(store)
export default function useStore() {
return useContext(context)
}
第一种方式是我们开发中常用的方法。
做一个异步小demo来测试
// person.js
import axios from 'axios'
import { makeAutoObservable, runInAction } from 'mobx'
class Person {
constructor() {
makeAutoObservable(this, null, { autoBind: true })
}
person = {}
getPersonInfo() {
runInAction(async () => {
const res = await axios('http://myjson.dit.upm.es/api/bins/irav')
this.person = res.data
})
}
}
export default new Person()
// 组件使用
import { observer } from 'mobx-react'
import React from 'react'
import person from '../store/test2'
function Test2() {
return (
<div>
<h1>展示个人信息</h1>
<p>{person.person.name}</p>
<p>{person.person.age}</p>
<button onClick={person.getPersonInfo}>点击获取</button>
</div>
)
}
export default observer(Test2)