前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
AI 面试
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
开发者导航
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
AI 面试
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
开发者导航

MobX核心概念解析,与React协同构建响应式应用的最佳实践

首页2024-11-26 12:40:12Front-End
ReactMobX状态管理前端工程化Redux

在前端应用开发中,状态管理一直是核心议题。随着应用复杂度不断攀升,如何高效、可维护地管理应用状态成为开发者必须面对的挑战。MobX 作为一款响应式状态管理库,以其简洁的API和优秀的性能表现赢得了众多开发者的青睐。本文将带你深入了解 MobX 的核心概念,并通过实际案例展示如何与 React 协同构建响应式应用。

# 一、为什么选择MobX

# 1.1 传统状态管理的痛点

在 MobX 出现之前,开发者通常使用 Redux 来管理应用状态。Redux 采用单一数据源模式,所有状态存储在一个只读的 store 中,只能通过 action 和 reducer 来更新状态。这种方式虽然逻辑清晰,但在实际应用中存在一些痛点:

状态冗余更新问题:由于 Redux 的状态是不可变的,每次更新都需要创建新的状态对象。这导致与 store 连接的 UI 组件都会重新渲染,即使它们只依赖状态中的部分数据。虽然可以使用 PureRenderMixin 或 reselect 来优化,但增加了额外的开发成本。

样板代码繁多:使用 Redux 需要编写大量的 action types、action creators、reducers,即使是一个简单的功能也需要完整的模板代码。

学习曲线陡峭:理解 Redux 的数据流需要掌握 action、reducer、middleware 等概念,对新手不太友好。

# 1.2 MobX的核心理念

MobX 采用了完全不同的设计理念:利用 JavaScript 的代理(Proxy)机制,自动追踪状态的变化并更新依赖它的组件。这种方式被称为响应式编程。

MobX 的核心理念可以概括为三点:

  1. 可观察的状态(Observable State):将普通的 JavaScript 对象转换为可观察的对象,任何对状态的修改都能被追踪。

  2. 自动推导(Computed Values):根据现有状态自动计算衍生值,类似 Excel 的公式功能。

  3. 响应式副作用(Reactions):当状态变化时自动执行副作用操作,如更新 UI、打印日志、发起网络请求等。

# 1.3 MobX与Redux对比

特性 MobX Redux
数据结构 多 store,支持普通对象 单一 store,不可变数据
更新方式 直接修改,可选严格模式 只能通过 action 和 reducer
依赖追踪 自动追踪,按需更新 手动订阅,可能过度渲染
代码量 简洁,样板代码少 较多模板代码
学习成本 较低 较高
调试工具 友好的时间旅行调试 强大的 DevTools

从数据流角度看,Redux 管理的是 STORE -> VIEW -> ACTION 的完整闭环,而 MobX 更关注 STORE -> VIEW 的部分。action 在 MobX 中是可选的,你可以直接修改状态,但这也带来了一个潜在问题:状态的修改入口不统一。

# 1.4 MobX的优缺点分析

优点

第一点是基于运行时的数据订阅。MobX 的数据依赖始终保持最小,而且是基于运行时自动追踪的。相比之下,使用 Redux 时可能一不小心就多订阅或者少订阅了数据,导致性能问题。因此在使用 Redux 时,我们需要借助 PureRenderMixin 以及 reselect 对 selector 做缓存优化。

第二点是通过面向对象的方式组织领域模型。OOP 的方式在某些场景下会比较方便,尤其是容易抽取 domain model 的时候。由于 MobX 支持引用方式引用数据,可以非常容易形成模型图(model graph),这有助于更好地理解应用结构。

第三点是修改数据方便自然。MobX 基于原生的 JavaScript 对象、数组和 Class 实现,修改数据不需要额外语法成本,也不需要始终返回一个新的数据,而是直接操作数据。

缺点

第一点是缺少最佳实践和社区积累。MobX 相对较新,遇到的问题可能社区都没有遇到过。并且,MobX 并没有很好的扩展和插件机制。

第二点是随意修改 store 的风险。我们都知道 Redux 里唯一可以改数据的地方是 reducer,这样可以保证应用的安全稳定;而 MobX 可以随意修改数据,触发更新,给人一种不安全的感觉。不过最新的 MobX 2.2 版本加入了 action 支持,并且开启 strict mode 之后,就只有 action 可以对数据进行修改,限制数据的修改入口,可以解决这个问题。

第三点是逻辑层的限制。如果更新逻辑不能很好地封装在 domain class 里,用 Redux 会更合适。另外,MobX 缺少类似 redux-saga 的库,业务逻辑的整合不知道放哪合适。

# 二、核心API详解

# 2.1 observable - 定义可观察状态

@observable 装饰器用于将 JavaScript 对象属性转换为可观察的属性。被装饰的属性会暴露出来供观察者使用,当属性值发生变化时,所有依赖该属性的地方都会收到通知。

Observable 值可以是 JavaScript 基本数据类型、引用类型、普通对象、类实例、数组和映射。

import { makeAutoObservable } from 'mobx'

class Counter {
  // 基本类型
  count = 0
  name = '计数器'

  // 引用类型
  user = {
    name: '张三',
    age: 25
  }

  // 数组
  items = []

  constructor() {
    // makeAutoObservable 会自动推断类型并添加相应的装饰器
    makeAutoObservable(this)
  }

  increment() {
    this.count++
  }

  decrement() {
    this.count--
  }

  setName(name) {
    this.name = name
  }
}

const counter = new Counter()
@前端进阶之旅: 代码已经复制到剪贴板

如果使用装饰器模式,需要配置 babel 支持:

// 使用装饰器写法
import { observable, computed, action } from 'mobx'

class Counter {
  @observable count = 0
  @observable title = 'this is about page'
  @observable num = 0

  // computed - 计算值
  @computed get getUserInfo() {
    return `我是computed经过计算的getter, current num: ${this.num}`
  }

  // action - 动作
  @action.bound add() {
    this.num++
  }

  @action.bound reduce() {
    this.num--
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

# 2.2 computed - 计算属性

计算值(Computed Values)是可以根据现有的状态或其它计算值衍生出的值。用于获取由基础 state 衍生出来的值,如果基础值没有变,获取衍生值时就会走缓存,这样就不会引起虚拟 DOM 的重新渲染。

import { makeAutoObservable, computed } from 'mobx'

class OrderStore {
  items = []
  taxRate = 0.1

  constructor() {
    makeAutoObservable(this, {
      // 显式声明 computed 属性
      subtotal: computed,
      tax: computed,
      total: computed
    })
  }

  get subtotal() {
    return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
  }

  get tax() {
    return this.subtotal * this.taxRate
  }

  get total() {
    return this.subtotal + this.tax
  }

  addItem(item) {
    this.items.push(item)
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

计算属性还支持 setter,用于实现逆向推导:

class Foo {
  @observable length = 2

  @computed get squared() {
    return this.length * this.length
  }

  set squared(value) {
    // 这是一个自动的动作,不需要注解
    // 设置 squared 时,自动推导出 length
    this.length = Math.sqrt(value)
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

# 2.3 action - 修改状态

只有 在 actions 中,才可以修改 MobX 中 state 的值。通过引入 MobX 定义的严格模式,可以强制使用 action 来修改状态,保证状态修改的可追溯性。

import { configure, makeAutoObservable, runInAction } from 'mobx'

// 开启严格模式 - 只有 action 可以修改状态
configure({ enforceActions: 'always' })

class Counter {
  count = 0

  constructor() {
    makeAutoObservable(this)
  }

  increment() {
    this.count++
  }

  decrement() {
    this.count--
  }

  async fetchData() {
    // 在异步操作中使用 runInAction
    const data = await fetch('/api/data').then(r => r.json())
    runInAction(() => {
      this.data = data
    })
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

runInAction 是一个实用的工具函数,用于在异步操作中批量更新状态:

import { runInAction } from 'mobx'

async function fetchUser() {
  const user = await api.getUser()

  // 所有的状态更新都放在 runInAction 中
  runInAction(() => {
    this.user = user
    this.loading = false
    this.error = null
  })
}
@前端进阶之旅: 代码已经复制到剪贴板

# 2.4 observer - 响应式组件

observer 是由 mobx-react 包提供的高阶组件,用于将 React 组件转换为响应式组件。在组件的 render 函数中使用的任何 observable 发生变化时,组件都会自动重新渲染。

observer 的原理是用 mobx.autorun 包装了组件的 render 函数,确保任何组件渲染中使用的数据变化时都可以强制刷新组件。

# 2.5 autorun - 自动运行

当可观察对象中保存的值发生变化时,可以在 mobx.autorun 中被观察到。autorun 在 observable 的值初始化或改变时自动运行。

如果你想响应式地产生一个可以被其它 observer 使用的值,请使用 @computed;如果你不想产生新值,而想要达到一个效果(如打印日志、发起网络请求),请使用 autorun。

# 2.6 reaction - 响应式副作用

# 2.7 flow - 异步流程管理

# 三、计数器完整示例

下面是一个完整的计数器示例,展示了如何将 MobX 与 React 结合使用:

# 五、总结


fe
  • 一、为什么选择MobX
    • 1.1 传统状态管理的痛点
    • 1.2 MobX的核心理念
    • 1.3 MobX与Redux对比
    • 1.4 MobX的优缺点分析
  • 二、核心API详解
    • 2.1 observable - 定义可观察状态
    • 2.2 computed - 计算属性
    • 2.3 action - 修改状态
    • 2.4 observer - 响应式组件
    • 2.5 autorun - 自动运行
    • 2.6 reaction - 响应式副作用
    • 2.7 flow - 异步流程管理
  • 三、计数器完整示例
  • 四、最佳实践
    • 4.1 Store 的组织方式
    • 4.2 严格模式配置
    • 4.3 组件中的使用方式
    • 4.4 异步操作最佳实践
    • 4.5 性能优化技巧
  • 五、总结

← React 18 并发机制深度解析大文件上传解决方案-基于Node.js的分片与断点续传实践 →