了解完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)