Mobx实现React 应用的状态管理详解

目录
  • MobX
  • 从一个 demo 开始
    • 创建类并将其转化成可观察对象
  • 使用可观察对象
  • MobX 与 React 集成
  • 在组件中使用可观察对象
    • 1. 访问全局的类实例
    • 2. 通过 props
    • 3. 通过 React Context
    • 4. 在组件中实例化 observable class 并存储它的实例
    • 5. 在组件中调用 observable 方法创建可观察对象
    • 6. 在函数组件中使用 useLocalObservable
  • 让组件具备观察能力
  • 总结

MobX

MobX 是一个状态管理库,它会自动收集并追踪依赖,开发人员不需要手动订阅状态,当状态变化之后 MobX 能够精准更新受影响的内容,另外它不要求 state 是可 JSON 序列化的,也不要求state 是 immutable,MobX 推荐的数据流如下图所示:

本文先以一个 demo 单独介绍 Mobx 的用法,再介绍如何将 Mobx 与 React 结合实现 React 应用程序的状态管理。

从一个 demo 开始

这部分用 MobX + TypeScript 实现一个 TODO List 的 demo,MobX 的版本为 6.5.0,TypeScript 的版本为 4.5.4,将 TypeScript 编译器配置项 useDefineForClassFields 设置为 true。

创建类并将其转化成可观察对象

创建 ToDoItem 类和 ToDoList 类,ToDoItem 类的代码如下:

import { makeObservable, observable, action } from 'mobx'
class ToDoItem {
    id: number
    name: string
    status: 0 | 1
    changeStatus(status: Status) {
        this.status = status
    }
    constructor(name: string) {
        this.id = Uid ++
        this.name = name
        this.status = 0
       // 注意这里
        makeObservable(this, {
            status: observable,
            changeStatus: action
        })
    }
}

用 makeObservable 将 ToDoItem 实例变成可观察的,用 observable 标记 status 字段,让 MobX 跟踪它的变化,changeStatus 方法用于修改 status 的值,所以用action标记它。

ToDoList 类比 ToDoItem 类复杂一些,它收集 Todo-List Demo 需要的全部数据,代码如下:

import { makeObservable, observable, action, computed, runInAction } from 'mobx'
class ToDoList {
    searchStatus?: 0 | 1
    list: ToDoItem[] = []
    get displayList() {
        if (!this.searchStatus) {
            return this.list
        } else {
            return this.list.filter(item => item.status === this.searchStatus)
        }
    }
    changeStatus(searchStatus: Status | undefined) {
        this.searchStatus = searchStatus
    }
    addItem(name: string) {
        this.list.push(new ToDoItem(name))
    }
    async fetchInitData() {
        await waitTime()
       // 注意这里
        runInAction(() => {
            this.list = [new ToDoItem('one'), new ToDoItem('two')]
        })
    }
    constructor() {
        makeObservable(this, {
            searchStatus: observable,
            list: observable,
            displayList: computed,
            changeStatus: action,
            addItem: action
        })
    }
}

与 ToDoItem 相比,ToDoList 多使用了 computed 标记,这是因为 displayList 的值由 searchStatus 和 list 通过一个纯函数计算而来,所以它被标记为 computed。fetchInitData 是一个异步方法,在其中用 runInAction 创建一个立即执行的 action 去修改 list 的值,从 fetchInitData 的实现可以看出,异步修改 state 和同步修改 state 没有差别,只要保证 state 是在 action 中修改的即可。

使用可观察对象

在上一步的 ToDoList 和 ToDoItem 的构造函数中,我们调用了 makeObservable 方法,并用合适的注解去标记实例字段,接下来用一段代码验证 MobX 是否按照要求跟踪state的变化。代码如下:

import { autorun} from 'mobx'
autorun(() => { console.log(toDoList.list.length) }) // line A
autorun(() => { console.log(toDoList.list) }) // line B

autorun 接收一个函数,该函数同步执行过程中访问的 state 或计算值发生变化时,它会自动运行,另外,调用 autorun 时,该函数也会运行一次。使用 toDoList.addItem 方法往 list 数组中 push 一个事项,你会发现上述 line A 的函数会运行,但是 line B 的函数不会运行;使用 toDoList.fetchInitData 方法给 list 数组赋值,line A 和 line B 的函数都会运行,出现这种差异是因为 autorun 使用全等(===)运算符确定两个值是否相等,但它认为 NaN 等于 NaN 。

用如下一段代码验证 MobX 是否按照要求跟踪 ToDoItem 实例的 state 的变化:

import { autorun} from 'mobx'
autorun(() => {
  if (toDoList.list.length) {
       console.log(toDoList.list[0]?.status)
   }
})
reaction(() => toDoList.list.length, () => {
  toDoList.list[0].changeStatus(1)// 修改status的值
})

当 reaction 的第一个参数返回 true 时,它的第二个参数会自动执行,上述代码在 reaction 中修改 toDoItem 的 status 字段,修改之后 autorun 能成功运行一次。

MobX 与 React 集成

现在将上一步的 TODO List 与 React 结合,为此需要安装 mobx-react-lite 或 mobx-react,mobx-react 比 mobx-react-lite 的功能更多,同时它的体积也更大,如果你的项目只使用函数组件,那么推荐安装 mobx-react-lite 而非 mobx-react,为了演示更多的用法本小节安装 mobx-react。另外,本小节会用到装饰器语法,所以要将 TypeScript 编译器配置项 experimentalDecorators 设置为true。下面是一个 MobX + React 的简单示例:

import { observer } from 'mobx-react'
import toDoList, { Status } from '../../mobx/todo'
const ToDoListDemoGlobalInstance= observer(
  class extends React.Component<{}, {}> {
    componentDidMount() {
     // 3s之后修改 searchStatus 的值
      setTimeout(() => {
        toDoList.changeStatus(Status.finished)
      }, 3000);
    }
    render() {
      return (
        <div>searchStatus: {toDoList.searchStatus}</div>
      )
    }
  }
)

observer 是一个高阶组件,它会订阅组件在渲染期间访问的可观察对象,可观察对象指的是用 makeAutoObservable 、makeObservable 或 observable 转换之后的对象,当组件渲染期间访问的 state 和计算值发生变化时,组件会重新渲染。上述代码,组件被装载 3s 后将修改 searchStatus 的值,由于 render 方法访问了 searchStatus 的值,所以组件会重新渲染。observer 除了以高阶组件的形式使用之外,还能以装饰器的形式使用。

在组件中使用可观察对象

下面介绍 6 种在组件中使用 MobX 可观察对象的写法。

1. 访问全局的类实例

上一个示例代码便是在组件中直接访问全局的类实例,在这里不再举更多的示例代码。

2. 通过 props

这种方式是指将可观察对象通过 props 的形式传递到组件中,代码如下:

import { observer } from 'mobx-react'
import toDoList, { Status } from '../../mobx/todo'
@observer
class ToDoListDemoByProps extends React.Component<{toDoList: ToDoList}, {}> {
  componentDidMount() {
    setTimeout(() => {
      toDoList.changeStatus(Status.finished)
    }, 3000);
  }
  render() {
   // 读取props中的可观察对象
    return (
      <div>ToDoListDemoByProps - searchStatus: {this.props.toDoList.searchStatus}</div>
    )
  }
}
//使用ToDoListDemoByProps
<ToDoListDemoByProps toDoList={toDoList}/>

3. 通过 React Context

这种方式是指通过 React Context 让可观察对象在整个被 Context.Provider 包裹的组件树中共享,代码如下:

import { observer } from 'mobx-react'
import toDoList, { Status, ToDoList } from '../../mobx/todo'
// 创建一个用observer包裹的函数组件
const ToDoListDemoByContext = observer(() => {
 // 在函数组件中使用Context
  const context = useContext(todoContext);
  useEffect(() => {
    setTimeout(() => {
      context.changeStatus(Status.finished)
    }, 3000);
  })
  return (
    <div>ToDoListDemoByContext - searchStatus: {context.searchStatus}</div>
  )
})
// 往Context传值
<todoContext.Provider value={toDoList}>
   <ToDoListDemoByContext/>
</todoContext.Provider>

4. 在组件中实例化 observable class 并存储它的实例

这种方式指的是在组件作用域中实例化类,并且将结果保存到组件的某个字段中,如果在函数组件中使用这种方式,那么还需要用到 useState。代码如下:

const ToDoListFuncDemoLocalInstance= observer(() => {
  // 实例化类
  const [ todoList ] = useState(() => new ToDoList())
  useEffect(() => {
    setTimeout(() => {
      // 使用实例方法更新状态
      todoList.changeStatus(Status.finished)
    }, 3000);
  })
  return (
    <div>ToDoListDemoLocalInstance - searchStatus: {todoList.searchStatus}</div>
  )
})

对于类组件而言,只需要将 new ToDoList() 的结果保存在它的实例属性上,之后在组件中访问该实例属性,代码如下:

@observer
class ToDoListClassDemoLocalInstance extends React.Component<{}, {}> {
  todoList = new ToDoList()
  // other
}

5. 在组件中调用 observable 方法创建可观察对象

这种方式不使用类去创建可观察对象,而是使用 observable 方法创建可观察对象,与第 4 种方式一样,如果在函数组件中还要用到 useState,代码如下

import { observable } from 'mobx'
const LocalObservableDemo = observer(() => {
 // 调用observable
  const [counter] = useState(() => observable({
    count: 0,
    addCount() {
      this.count ++
    }
  }))
  return <>
    <div>{counter.count}</div>
    <button onClick={() => counter.addCount()}>add</button>
  </>
})

上述代码使用 mobx 导出的 observable 方法创建一个可观察对象,并在函数组件使用该对象,当它的 count 属性值发生变化时,组件将重新渲染。对于类组件而言只需要将 observable 函数的结果保存到实例属性上即可。

6. 在函数组件中使用 useLocalObservable

useLocalObservable 是 useState + observable 简写版本,只能在函数组件中使用,代码如下:

import { observer, useLocalObservable } from 'mobx-react'
const UseLocalObservableDemo = observer(() => {
  const counter = useLocalObservable(() => ({
    count: 0,
    addCount() {
      this.count ++
    }
  }))
  return <>
    <div>{counter.count}</div>
    <button onClick={() => counter.addCount()}>add</button>
  </>
})

对于函数组件而言,useLocalObservable 只是一个自定义Hook,它返回一个可观察对象。

有多种方式让组件在渲染阶段使用可观察对象,不管是哪种方式,组件都必须具备观察能力,否则,当渲染期间访问的 state 和计算值发生变化时,组件不会重新渲染。笔者在使用 MobX 做状态管理时,最常用的方式是第 1 和第 2 种。第 4、5、6 种方式必要性不大。

让组件具备观察能力

observer 是让组件具备观察能力最常见的方式,在这里介绍另一种让组件具备观察能力的方式,即:Observer组件。用法如下:

import { Observer } from 'mobx-react'
class ObservableDemo extends React.Component<{},{}> {
  render() {
    return (
    <>
      <div>{toDoList.searchStatus || '-'}</div>  {/** lineA */}
      <Observer>
	{() => <div>{toDoList.searchStatus || '-'}</div>} {/** lineB */}
	</Observer>
    </>
    )
  }
}

Observer 组件会创建一个匿名的观察区域,在上述代码中,如果 toDoList.searchStatus 的值发生变化,那么 lineB 会重新渲染,但是 lineA 不会重新渲染。

总结

将 MobX 与 React 结合在一起的关键在于用 observer 包裹组件以及在组件中读取可观察对象,observer 不关心可观察对象从哪里来,也不关心如何读取可观察对象,只关心在组件中可观察对象是否可读。对于习惯面向对象编程的工程师而言,用 MobX 做状态管理会比用 Redux 做状态管理更得心应手。

以上就是Mobx 实现 React 应用的状态管理的详细内容,更多关于Mobx React 应用状态管理的资料请关注我们其它相关文章!

(0)

相关推荐

  • 前端React Nextjs中的TS类型过滤实用技巧

    目录 自我介绍 分步介绍 开胃小菜 keyof in Conditional 泛型 正餐开始 实战应用例子 最后 大家好,我是零一,相信大家在阅读同事写的代码或者优秀的开源库的代码时,一定见过各种各样的风骚的TS写法,不花点时间下去根本看不懂,换作是我们,可能就直接一个 any 完事了,但是真正当项目体积变大后,你会发现这些 TS骚操作真的很重要,因为它能很好地帮助你做静态类型校验 自我介绍 TS类型过滤,英文名(我自己取的)叫 FilterConditionally,这是它完整的样子 type

  • 更强大的React 状态管理库Zustand使用详解

    目录 介绍 创建项目项目 安装项目依赖 创建项目结构 设置环境变量 服务 设置 store 清除/重置存储 介绍 在这篇文章中,我会介绍 Zustand 在实际项目中的使用. 我会构建一个 GitHub 用户搜索项目,在项目中通过调用 GitHub API 来实现搜索用户的功能.我还会并演示如何直接从 Zustand 存储中进行 API 调用,并将状态持久化到 sessionStorage 或 localStorage 中. 完成效果如下: 创建项目项目 首先,我们需要创建一个新的 React

  • ReactJS中不同类型的状态详解

    目录 引言 误解 服务器端状态与客户端状态 什么是React Query? 总结 引言 在这篇文章中,我们将介绍ReactJS中不同类型的状态,以及我们是如何轻易地将这些类型混为一谈的.我们还将讨论哪种状态应该使用哪种工具/库/包.这篇文章将充满了多汁的技术,所以要保持专注.所以不要浪费任何时间,让我们开始吧 误解 在已经配置好redux的情况下,利用redux的一个误区是当开发者开始为他们应用中的所有状态使用redux时,这样做是为了避免道具的钻取,并通过用商店包装你的应用,在你的应用的各个层

  • vue3+ts中ref与reactive指定类型实现示例

    目录 ref 的基础特性 如何在ref中指定类型 reactive isRef.isReactive toRef.toRefs.toRaw ref 的基础特性 ref 约等于 reactive({ value: x }) ref() 可以定义时无参数,第一次赋值任意类型,然后就不能增加属性 const refa = ref(6) const rcta = reactive({ value: 12 }) console.log('refa:', refa) //RefImpl{...} conso

  • React Native可复用 UI分离布局组件和状态组件技巧

    目录 引言 包装 Context.Provider 作为父组件 使用 Context Hook 来实现子组件 使用 React 顶层 API 动态设置样式 复用 Context,实现其它子组件 抽取共同状态逻辑 自由组合父组件与子组件 示例 引言 单选,多选,是很常见的 UI 组件,这里以它们为例,来讲解如何分离布局组件和状态组件,以实现较好的复用性. 假如我们要实现如下需求: 这类 UI 有如下特点: 不管是单选还是多选,都可以有网格布局,我们可以把这个网格布局单独抽离出来,放到一个独立的组件

  • Mobx实现React 应用的状态管理详解

    目录 MobX 从一个 demo 开始 创建类并将其转化成可观察对象 使用可观察对象 MobX 与 React 集成 在组件中使用可观察对象 1. 访问全局的类实例 2. 通过 props 3. 通过 React Context 4. 在组件中实例化 observable class 并存储它的实例 5. 在组件中调用 observable 方法创建可观察对象 6. 在函数组件中使用 useLocalObservable 让组件具备观察能力 总结 MobX MobX 是一个状态管理库,它会自动收

  • 基于React Hooks的小型状态管理详解

    目录 实现基于 React Hooks 的状态共享 使用感受 本文主要介绍一种基于 React Hooks 的状态共享方案,介绍其实现,并总结一下使用感受,目的是在状态管理方面提供多一种选择方式. 实现基于 React Hooks 的状态共享 React 组件间的状态共享,是一个老生常谈的问题,也有很多解决方案,例如 Redux.MobX 等.这些方案很专业,也经历了时间的考验,但私以为他们不太适合一些不算复杂的项目,反而会引入一些额外的复杂度. 实际上很多时候,我不想定义 mutation 和

  • vue前端开发辅助函数状态管理详解示例

    目录 mapState mapGetters mapMutations mapActions 示例 小结 mapState 当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余.为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性.不使用mapState时,获取对象状态,通常放在使用组件的computes属性中,使用方式为: //.... computed: { count: function(){ return this.$store.state

  • flutter自定义InheritedProvider实现状态管理详解

    目录 InheritedWidget简单数据驱动模型 1. 数据存储 2. 变更通知 3. 使用方法 InheritedWidget简单数据驱动模型 基于InheritedWidget,实现简单的数据驱动模型,模型结构如下: 1. 数据存储 使用 InheritedWidget,新建 InheritedProvider import 'package:flutter/material.dart'; class InheritedProvider<T> extends InheritedWidg

  • javascript Redux的状态管理详解

    所谓的状态管理,就是对应用程序中的数据进行管理. 理念:凡是数据流管理混乱的项目,几乎都上不了线.好的项目,必须有非常良好的数据流管理. 如何使用Redux?记住“3个3”. 第1个三:3个api,createStore.combineReducers.applyMiddleware 第2个三:3个特点,store单一数据源.store只读的.只能通过reducer纯函数来修改store. 第3个三:3个概念,store.action.reducer. 基本概念: state 包含所有数据,用来

  • vue3如何使用provide实现状态管理详解

    目录 前言 如何通过 provide/inject 实现 Vuex的功能 在应用中注册此插件 插件的入口文件 创建 store ,把对应的数据挂载到根组件上 实现 mapState.mapMutations 和 mapActions方法 组件中使用 总结 前言 在 Vue 生态中, Vuex 这个官方的状态管理库在 Vue 应用开发中,为我们带来了非常便捷的功能.但是 Vuex 20K+ 的大小,也带来了一些成本,对于项目规模较小的应用来说, 引入 Vuex 只是为了存储用户信息之类的一小撮数据

  • vue之使用vuex进行状态管理详解

    目录 vuex进行状态管理 vuex状态管理基本使用 vuex进行状态管理 首先学习vuex必须先知道vue原理 Vue是一个典型的MVVM框架,模型(Model)只是普通的JavaScript对象,修改它则视图(View)会自动更新.这种设计让状态管理变得非常简单而直观 Vue实现这种数据双向绑定的效果,需要三大模块: Observer:能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者 Compile:对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更

  • React DnD如何处理拖拽详解

    目录 正文 代码结构 DndProvider DragDropManager useDrag HTML5Backend TouchBackend 总结 正文 React DnD 是一个专注于数据变更的 React 拖拽库,通俗的将,你拖拽改变的不是页面视图,而是数据.React DnD 不提供炫酷的拖动体验,而是通过帮助我们管理拖拽中的数据变化,再由我们根据这些数据进行渲染.我们可能需要写额外的视图层来完成想要的效果,但是这种拖拽管理方式非常的通用,可以在任何场景下使用.初次使用可能感觉并不是那

  • React redux 原理及使用详解

    目录 概述 createStore创建store applyMiddleware 应用中间件 combineReducers 合并多个reducer dispatch 中间件 中间件的调用顺序 store redux 数据流 bindActionCreators compose enhancer 使用 redux 常遇见的问题 概述 一个状态管理工具 Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个 Store. State:包含所有数据,如果想得到某个时点的数据,就要对

  • Crashlytics Android 异常报告统计管理(详解)

    简介 Crashlytic 成立于2011年,是专门为移动应用开者发提供的保存和分析应用崩溃信息的工具.Crashlytics的使用者包括:支付工具Paypal, 点评应用Yelp, 照片分享应用Path, 团购应用GroupOn等移动应用. 2013年1月,Crashlytics被Twitter收购,成为又一个成功的创业产品.被收购之后,由于没有了创业公司的不稳定因素,我们更有理由使用它来分析应用崩溃信息. 使用Crashlytics的好处有: 1.Crashlytics不会漏掉任何应用崩溃信

随机推荐