【状态管理库系列】一杯咖啡的时间,学会【Mobx】

简介: 【状态管理库系列】一杯咖啡的时间,学会【Mobx】

前言

前阵子学了一遍Mobx,入门非常简单,对于Vue开发者来说很是友好,用起来也没有Redux那么。迫不及待的想写下这篇文章,记录一下成长脚印,如果能帮到你就更好了。

原本想好好捣鼓一下mobx源码的,但又担心自己还没搞懂万一误人子弟了怎么办,还是等把mobx原理篇搞清楚了,再手动实现一个mini-mobx吧。(立个flag)

本篇介绍版本mobx^6

一) Mobx 概述(说点官话)

MobX是一个旨在通过透明地应用函数式反应式编程 (TFRP) 使状态管理变得简单和可扩展的库。根据自述文件,MobX 的方法是这样的:“任何可以从应用程序状态派生的东西,都应该派生出来。自动地。 ” 这包括 UI、数据序列化、服务器通信等。

此外,MobX 的一些核心原则包括:

  • 可能有多个 store 来存储应用程序的状态
  • 整个应用程序的状态存储在单个对象树中
  • 任何一段代码中的动作都可以改变应用程序的状态
  • 当状态改变时,状态的所有派生都会自动和原子地更新

Mobx可以独立于任何框架而存在,但通常人们把它和React来绑定使用。

image.png

mobx的几个优点

image.png

社区评价

总结就是一句话,简单好用~image.png

二) Mobx 工作流程图

image.png

「首先从左往右看,事件触发了 Actions,Actions 作为唯一修改 State 的方式,修改了 State,State 的修改更新了计算值 Computed,计算值的改变引起了 Reactions 的改变,导致了 UI 的改变(re-render),Reactions 可以经过事件调用 Actions。」

几个概念

  1. Observable state(可观察的状态)
  2. Computed values(计算值)
  3. Reactions(反应)
  4. 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()

实现步骤:

  1. 定义数据状态(state)
  2. 数据响应式处理
  3. 定义action函数  (修改数据)
  4. 实例化并导出实例

通过中间件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中存在mutationsactions可以在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 模块化

一个项目有很多业务模块,我们不能把所有的代码都写到一起这样很难维护,为了提供可维护性,需要引入模块化。也就是说可以有多个仓库,多个仓库之前相互独立,互不干扰。

image.png

代码实现

拆分模块,每个模块拥有其独立的state/actionsimage.png

src/store/index.js中进行模块组合,自定义useStore方法使用useContext统一将根实例上的state/actions暴露出来,方便我们使用。image.png

使用

// 导入定义好的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++
        }
    }))
    ....
}

image.png

四) 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原理解读可以放在评论区下面吗?

image.png

参考


相关文章
|
1月前
|
数据挖掘
uniapp uview扩展u-picker支持日历期间 年期间 月期间 时分期间组件
uniapp uview扩展u-picker支持日历期间 年期间 月期间 时分期间组件
68 10
|
存储 JSON 算法
一杯茶的时间入门Vue新的状态管理库-2
Pinia 插件 Pinia 生态已有许多插件,可以扩展更多功能: pinia-plugin-persistedstate:数据持久化 pinia-plugin-debounce:防抖修改状态 pinia-plugin-pinia-observable:转换成 Observable
110 0
QGS
|
6月前
|
存储 JavaScript 容器
浅学状态管理VueX
浅学状态管理VueX
QGS
52 2
|
6月前
|
存储 资源调度 JavaScript
探索 Vuex 的世界:状态管理的新视角(上)
探索 Vuex 的世界:状态管理的新视角(上)
探索 Vuex 的世界:状态管理的新视角(上)
|
6月前
|
存储 缓存 JavaScript
探索 Vuex 的世界:状态管理的新视角(下)
探索 Vuex 的世界:状态管理的新视角(下)
探索 Vuex 的世界:状态管理的新视角(下)
|
6月前
|
存储 前端开发 JavaScript
探索 Vuex 的世界:状态管理的新视角(中)
探索 Vuex 的世界:状态管理的新视角(中)
探索 Vuex 的世界:状态管理的新视角(中)
|
6月前
|
存储 缓存 JavaScript
第十三章:vuex状态(数据)管理
第十三章:vuex状态(数据)管理
66 0
|
6月前
|
资源调度
状态管理之Vuex (一) 基操勿六
状态管理之Vuex (一) 基操勿六
37 0
|
前端开发
🔔叮~,你有几个系统级交互的React hooks待查收
🔔叮~,你有几个系统级交互的React hooks待查收
|
存储 JavaScript API
一杯茶的时间入门Vue新的状态管理库-1
Pinia 的优势 相比 Vuex,Pinia 有以下优点:
123 3