前言
前阵子学了一遍Mobx,入门非常简单,对于Vue开发者来说很是友好,用起来也没有Redux那么重。迫不及待的想写下这篇文章,记录一下成长脚印,如果能帮到你就更好了。
原本想好好捣鼓一下mobx源码的,但又担心自己还没搞懂万一误人子弟了怎么办,还是等把mobx原理篇搞清楚了,再手动实现一个mini-mobx吧。(立个flag)
本篇介绍版本mobx^6
一) Mobx 概述(说点官话)
MobX是一个旨在通过透明地应用函数式反应式编程 (TFRP) 使状态管理变得简单和可扩展的库。根据自述文件,MobX 的方法是这样的:“任何可以从应用程序状态派生的东西,都应该派生出来。自动地。 ” 这包括 UI、数据序列化、服务器通信等。
此外,MobX 的一些核心原则包括:
- 可能有多个 store 来存储应用程序的状态
- 整个应用程序的状态存储在单个对象树中
- 任何一段代码中的动作都可以改变应用程序的状态
- 当状态改变时,状态的所有派生都会自动和原子地更新
Mobx可以独立于任何框架而存在,但通常人们把它和React来绑定使用。
mobx的几个优点
社区评价
总结就是一句话,简单好用~
二) Mobx 工作流程图
「首先从左往右看,事件触发了 Actions,Actions 作为唯一修改 State 的方式,修改了 State,State 的修改更新了计算值 Computed,计算值的改变引起了 Reactions 的改变,导致了 UI 的改变(re-render),Reactions 可以经过事件调用 Actions。」
几个概念
- Observable state(可观察的状态)
- Computed values(计算值)
- Reactions(反应)
- Actions(动作)
三) Mobx基本使用
3.1 安装Mobx
- mobx 核心库
- mobx-react-lite 仅支持函数式组件
- mobx-react 支持函数组件和类组件
mobx-react-lite/mobx-react
可以理解为:一个链接React和Mobx的中间件。
yarn i mobx@6 mobx-react-lite pnpm add mobx@6 mobx-react-lite
3.2 创建第一个仓库【计数器案例】
需求:使用Mobx实现计数器案例,mobx负责计数逻辑,react负责渲染和事件触发
目录 src/store/index.js
import { makeAutoObservable, computed } from "mobx"; class CounterStore { // 定义仓库数据 count = 0 constructor() { // 响应式处理 makeAutoObservable(this) } // 定义行为 action increment () { this.count++ } decrement () { this.count-- } } export default new CounterStore()
实现步骤:
- 定义数据状态(state)
- 数据响应式处理
- 定义action函数 (修改数据)
- 实例化并导出实例
通过中间件mobx-react-lite
连接React视图,使得React可以使用Mobx中的响应式数据。
目录:src/App.jsx
// 导入定义好的store import counterStore from './store' import { observer } from 'mobx-react-lite' function App() { return ( <> <h2>count : {counterStore.count}</h2> <button onClick={() => counterStore.decrement()}>-1</button> <button onClick={() => counterStore.increment()}>+1</button> </> ) } // 使用observer包裹组件,让其响应数据变化 export default observer(App)
observer
HOC 将自动订阅 React components 中任何 在渲染期间 被使用的 可被观察的对象 。 因此, 当任何可被观察的对象 变化 发生时候 组件会自动进行重新渲染(re-render)
3.3 计算属性computed
概念:基于现有的数据做计算得到新的数据,并且可以在依赖的数据发生变化时立刻进行计算。类似于Vue
中的computed。
定义计算属性步骤:
- 定义get计算属性
- 在makeAutoObservable中进行声明
import { makeAutoObservable, computed } from "mobx"; class CounterStore { // 定义仓库数据 count = 0 list = [1,2,3,4] constructor() { // 声明其为计算属性 makeAutoObservable(this, { filterList: computed, doubleCount: computed }) } get filterList () { return this.list.filter(num => num > 2) } get doubleCount () { return this.count * 2 } ... } export default new CountStore()
3.4 唯一修改state状态的行为action
在mobx中,只能通过action来修改state,这样可以进行时间回溯。能够追溯每一次的更改来源,也避免了因为一些不恰当的修改state,导致状态混乱等问题。
当你直接修改state的时候,浏览器会出现下面这样的警告。
<button onClick={() => counterStore.count++}>+1</button>
[MobX] Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed....
所以请记住,在mobx中修改state,请使用action来触发更新,避免直接操作state。
3.5 异步操作
不同于Vuex
中存在mutations
和actions
可以在actions中进行异步操作,而状态追溯的制作放在mutations中。在mobx中只有一个actions概念,那么如何做到异步可追溯呢 ?
在 MobX 中,不管是同步还是异步操作,都可以放到 action 中,但是最后执行结果的赋值操作必须在runInAction
中进行。
import { runInAction } from 'mobx' ... delayIncrement = async () => { await new Promise(resolve => { setTimeout(() => { resolve() }, 1000) }) // runInAction(() => { this.count++ }) } ...
3.6 模块化
一个项目有很多业务模块,我们不能把所有的代码都写到一起这样很难维护,为了提供可维护性,需要引入模块化。也就是说可以有多个仓库,多个仓库之前相互独立,互不干扰。
代码实现
拆分模块,每个模块拥有其独立的state/actions
在src/store/index.js
中进行模块组合,自定义useStore方法使用useContext
统一将根实例上的state/actions
暴露出来,方便我们使用。
使用
// 导入定义好的store import { useStore } from './store/index' import { observer } from 'mobx-react-lite' function App() { const { listStore, counterStore } = useStore() return ( <> .... </> ) } // 使用observer包裹组件,让其响应数据变化 export default observer(App)
3.7 Vue选手福音
mobx中的observable
可以创建一个响应式对象,配上useState
来保存store
的引用地址值,就可以让我们在React
项目可以做到Vue
中的响应式数据data
一般。
定义组件内部的私有状态,而不需要关系具体状态是如何发生变化的。
import React, { useState } from 'react' import { observable } from 'mobx' import { observer } from 'mobx-react-lite' function App() { const [store] = useState(() => observable({ count: 0, increment() { this.count++ }, }) ) return ( <> <h2>count:{store.count}</h2> <button onClick={() => store.increment()}>+1</button> </> ) } // 使用observer包裹组件,让其响应数据变化 export default observer(App)
const [store] = useState(() => observable({ /* something */}))
是非常通用的一套写法,也可以通过 useLocalObservableHook 来简化上述代码。
... import { useLocalObservable } from 'mobx' ... function App() { const [store] = useLocalObsevable(()=>({ count: 0, increment() { this.count++ } })) .... }
四) Mobx中其他常用API介绍
这部分内容主要是介绍一些除了上述代码中出现的Mobx-api,方便大家快速上手项目。附上中文文档地址
4.1 autorun
用法:autorun(() => effect, options?)
在被其观察的任意一个值发生改变时重新执行一个函数。
var numbers = observable([1,2,3]); var sum = computed(() => numbers.reduce((a, b) => a + b, 0)); var disposer = autorun(() => console.log(sum.get())); // 输出 '6' numbers.push(4); // 输出 '10' disposer(); numbers.push(5); // 不会再输出任何值。`sum` 不会再重新计算。
4.2 reaction
用法:reaction(() => data, data => effect, options?)
reaction 与 autorun 的作用类似,都是追踪可观察对象的变化然后运行。
接收两个回调函数作为参数,其中第一个参数用来调用可观察对象,并计算出需要处理的”值“, 第二个参数接收第一个回调函数 return 的值,并作出响应。
注意:与 autorun 不同的是,第二个回调函数不会在创建时立即调用,只会在第一个函数返回新值时触发。
import { observable, reaction } from 'mobx'; const person = observable({ name: '邵小白', age: 21 }) reaction(() => person.name, (newVal, val) => { console.log(newVal, val) // 1024shao 邵小白 }) person.age = 18 // 不会引起变化 person.name = '1024shao'
Reaction 接收第三个参数,它是一个参数对象,有如下可选的参数:
fireImmediately
: 布尔值,用来标识效果函数是否在数据函数第一次运行后立即触发。默认值是false
。delay
: 可用于对效果函数进行去抖动的数字(以毫秒为单位)。如果是 0(默认值) 的话,那么不会进行去抖。equals
: 默认值是comparer.default
。如果指定的话,这个比较器函数被用来比较由 数据 函数产生的前一个值和后一个值。只有比较器函数返回 false 效果 函数才会被调用。此选项如果指定的话,会覆盖compareStructural
选项。name
: 字符串,用于在例如像spy
这样事件中用作此 reaction 的名称。onError
: 用来处理 reaction 的错误,而不是传播它们。scheduler
: 设置自定义调度器以决定如何调度 autorun 函数的重新运行
4.3 when
用法:when(() => condition, () => effect, options?)
或 await when(() => condition, options?)
一旦一个 observable 条件为真就立即执行一次副作用函数。
- 第一个函数:根据可观察数据返回一个布尔值。当该布尔值为true时,执行第二个函数,且只执行一次。
- 第二个函数:如果可观察数据返回的布尔值一开始就是true,那么立即同步执行第二个函数。
import { observable, when } from 'mobx'; let store = observable({ isLoading: true }) when(() => !store.isLoading, () => { console.log('加载结束') // 2S 之后输出 }) setTimeout(() => { store.isLoading = false }, 2000)
如果没提供 effect
函数,when
会返回一个 Promise
。
配合async/awit
使用
async function() { await when(() => !store.isLoading) // 等等.. } ···
推荐TODO-MVC
练手项目github.com/mobxjs/mobx…
总结
感谢看到最后,觉得有帮助的话点个赞再走吧~
关于mobx
的基本使用到这里就结束了,等过俩星期上手公司项目之后遇到了新的难题,就再写一篇关于mobx系列的文章吧。哦,对了。如果大家有好的mobx6
原理解读可以放在评论区下面吗?