0

0

JS 函数响应式编程 - 使用 MobX 实现自动依赖跟踪的状态管理

紅蓮之龍

紅蓮之龍

发布时间:2025-09-21 22:41:01

|

157人浏览过

|

来源于php中文网

原创

MobX通过observable、action、computed和reaction实现自动依赖跟踪,利用Proxy或defineProperty拦截数据读写,构建响应式依赖图,状态变化时精准更新依赖项。

js 函数响应式编程 - 使用 mobx 实现自动依赖跟踪的状态管理

MobX提供了一种直观且高效的方式,通过其独特的响应式系统,让JavaScript中的状态管理变得异常简洁。它能自动追踪哪些数据被使用,并在这些数据变化时,精准地更新所有依赖它的部分,极大地简化了传统手动订阅-发布模式的复杂性。对我来说,这就像给你的应用状态装上了“自动驾驶”系统,你只管改变状态,MobX负责让一切保持同步,这种体验真的能让开发者感到一种前所未有的自由。

解决方案

要使用MobX实现自动依赖跟踪的状态管理,核心在于理解并运用它的几个基本概念:

observable
action
computed
reaction

首先,我们需要将那些需要被追踪变化的数据标记为

observable
。这可以是任何JavaScript数据类型:对象、数组、Map、Set,甚至是原始值。当一个
observable
数据被读取时,MobX会默默地记录下是谁在读取它;当它被修改时,MobX会通知所有之前记录的“读者”进行更新。

import { makeObservable, observable, action, computed } from 'mobx';

class CounterStore {
  count = 0; // 这是一个普通属性

  constructor() {
    // 使用 makeObservable 标记哪些属性和方法是 observable, action, computed
    makeObservable(this, {
      count: observable,
      increment: action,
      decrement: action,
      doubleCount: computed,
    });
  }

  increment() {
    this.count++;
  }

  decrement() {
    this.count--;
  }

  // computed 属性会缓存其结果,只有当依赖的 observable 变化时才会重新计算
  get doubleCount() {
    console.log('Calculating doubleCount...'); // 观察何时重新计算
    return this.count * 2;
  }
}

const counter = new CounterStore();

接下来是

action
。虽然可以直接修改
observable
属性,但MobX推荐使用
action
来封装所有修改状态的逻辑。这不仅能提供更好的结构化,还能让MobX在性能优化上做得更好,比如批量更新。当一个
action
执行时,MobX会暂停所有依赖的更新,直到
action
完成,然后一次性通知所有受影响的部分。

computed
属性则非常巧妙,它允许你从现有
observable
状态派生出新的状态。最棒的是,
computed
属性是惰性求值的,并且会缓存结果。只有当它所依赖的
observable
发生变化时,
computed
属性才会重新计算。这对于避免不必要的重复计算至关重要,也是性能优化的一个核心点。

最后,

reaction
(包括
autorun
reaction
when
等)是MobX用来处理副作用的机制。当你想在某个
observable
状态变化时执行一些非状态修改的操作,比如打印日志、发起网络请求或者更新DOM(在React组件外部),
reaction
就派上用场了。

import { autorun } from 'mobx';

// autorun 会在初始化时运行一次,然后在其依赖的 observable 变化时再次运行
autorun(() => {
  console.log(`Current count is: ${counter.count}`);
  console.log(`Double count is: ${counter.doubleCount}`);
});

// 模拟用户操作
counter.increment(); // 会触发 autorun 重新运行
counter.increment(); // 再次触发
counter.decrement(); // 再次触发

// 如果没有组件,我们也可以手动触发一些操作来观察 MobX 的响应
// 比如在某个时间点后改变状态
setTimeout(() => {
  counter.increment();
  console.log("After timeout, count incremented.");
}, 1000);

在实际的React应用中,我们会用

mobx-react
mobx-react-lite
库提供的
observer
高阶组件或hook来连接MobX store和React组件。
observer
会确保当组件所依赖的
observable
状态发生变化时,组件能够自动重新渲染,而且是“精准”的重新渲染,只更新那些真正需要更新的组件。

// 假设在 React 组件中
import React from 'react';
import { observer } from 'mobx-react-lite'; // 或者 mobx-react

const CounterDisplay = observer(() => {
  console.log('CounterDisplay rendered'); // 观察何时重新渲染
  return (
    

Count: {counter.count}

Double Count: {counter.doubleCount}

); }); // 在你的 App.js 中使用

这就是MobX的基本工作流,它把我们从手动管理状态依赖的泥潭中解放出来,让我们能更专注于业务逻辑本身。

为什么选择MobX而非其他状态管理库?它解决了哪些痛点?

谈到状态管理,市面上方案不少,Redux、Zustand、Jotai等等。但对我个人而言,MobX之所以能脱颖而出,很大程度上是因为它解决了传统状态管理模式中那些令人头疼的痛点,尤其是“手动依赖追踪”和“样板代码过多”的问题。

想想看,过去我们写应用,尤其是在没有强大响应式框架支持的时候,每当一个数据变了,我们得手动去通知所有使用这个数据的地方更新UI,或者执行一些副作用。这就像你家里有个开关,每次开灯,你还得手动去给每个灯泡发送指令。一旦项目复杂起来,这种手动追踪依赖关系简直是噩梦,很容易漏掉某个地方,导致UI不同步,或者因为过度更新而造成性能问题。Redux虽然提供了单向数据流和可预测性,但它要求你显式地定义

action
reducer
,处理
immutable
数据,这无疑引入了大量的样板代码,尤其对于一些简单的状态修改,感觉就像是杀鸡用牛刀。

MobX的出现,就像给这个场景引入了“智能家居系统”。它通过自动依赖追踪机制,彻底解决了手动通知的烦恼。你只需要把数据标记为

observable
,然后正常地修改它,MobX会在幕后默默地监听所有对这些
observable
数据的读取操作,构建一个依赖图。当数据变化时,它会精准地通知那些真正依赖这些数据的组件或
reaction
重新执行。这种“所见即所得”的直观性,让开发者可以像操作普通对象一样操作状态,极大地降低了心智负担。

它还解决了性能问题。很多状态管理方案在状态变化时,可能会导致大范围的组件重新渲染。而MobX由于其细粒度的依赖追踪,能够做到精准更新,只重新渲染那些真正受到影响的组件,这在大型复杂应用中,对性能的提升是显而易见的。对于我来说,这种“少写代码,多做事情”的哲学,以及它带来的性能优势,是选择MobX的关键驱动力。它让状态管理变得更像是一种自然而然的事情,而不是一个需要小心翼翼维护的复杂系统。

MobX的响应式原理揭秘:它是如何实现自动依赖跟踪的?

MobX实现自动依赖跟踪的核心机制,在于它能够“拦截”对

observable
数据的访问和修改。这听起来有点像魔法,但背后其实是JavaScript语言特性和一些巧妙的数据结构在支撑。

Bardeen AI
Bardeen AI

使用AI自动执行人工任务

下载

在现代浏览器环境中,MobX主要利用了ES6的

Proxy
对象。
Proxy
允许你创建一个对象的代理,然后拦截对这个代理对象的所有操作,包括属性的读取(
get
)、写入(
set
)、删除等等。当我们将一个对象标记为
observable
时,MobX实际上会为这个对象创建一个
Proxy

具体来说,当一个被

observable
化的属性被“读取”时(例如,在一个
autorun
函数或React组件中),
Proxy
get
陷阱会被触发。MobX会在这里记录下当前正在执行的“反应”(
autorun
computed
observer
组件等)与这个
observable
属性之间的依赖关系。它会构建一个内部的“订阅者-发布者”模式,但这个模式是自动构建的。

而当这个

observable
属性被“修改”时,
Proxy
set
陷阱会被触发。MobX会检测到这个变化,然后查找之前记录的依赖图,找出所有依赖这个属性的“反应”,并通知它们需要重新执行或重新渲染。这个过程是高度优化的,MobX会进行批量更新,避免在短时间内因多次状态修改而导致频繁的更新操作。

对于不支持

Proxy
的环境(或者为了兼容性),MobX会退而求其次,使用
Object.defineProperty
来劫持属性的
getter
setter
。原理类似,但
Proxy
提供了更强大的拦截能力,能处理更复杂的数据结构,比如动态添加的属性,这让MobX在实现上更加灵活和强大。

这种机制的精妙之处在于,开发者不需要显式地去订阅或取消订阅,MobX在运行时动态地构建和维护这个依赖图。你只需要像操作普通JavaScript数据一样去操作你的状态,MobX就会在背后为你处理好所有的响应式更新。这种透明的、侵入性极低的设计,是MobX能够提供如此流畅开发体验的关键。它就像一个隐形的管家,默默地观察着你的数据,并在你需要的时候,精准地做出响应。

在实际项目中,如何优雅地组织MobX Store?

在实际项目中,尤其当应用规模逐渐增大时,如何优雅地组织MobX Store就变得至关重要。我见过一些项目,因为Store组织不当,导致代码难以维护,甚至影响了团队协作效率。我的经验是,没有银弹式的完美方案,但有一些原则和模式可以遵循,让你的MobX Store保持清晰、可扩展和易于测试。

首先,模块化和领域驱动设计是核心。不要把所有状态都塞到一个巨大的Store里。而是根据业务领域或功能模块,将Store拆分成更小、更专注的单元。比如,你可以有一个

AuthStore
处理用户认证,一个
ProductStore
管理商品数据,一个
CartStore
处理购物车逻辑。每个Store只负责其领域内的状态和业务逻辑,这样能有效降低Store之间的耦合度,提升内聚性。

// stores/AuthStore.js
import { makeAutoObservable } from 'mobx';

class AuthStore {
  user = null;
  isLoading = false;

  constructor() {
    makeAutoObservable(this);
  }

  *login(credentials) { // 使用 generator function 处理异步
    this.isLoading = true;
    try {
      // 模拟 API 调用
      const response = yield new Promise(resolve => setTimeout(() => resolve({ username: credentials.username }), 1000));
      this.user = response;
    } catch (error) {
      console.error("Login failed:", error);
    } finally {
      this.isLoading = false;
    }
  }

  logout() {
    this.user = null;
  }

  get isLoggedIn() {
    return !!this.user;
  }
}

export const authStore = new AuthStore();

// stores/ProductStore.js
import { makeAutoObservable } from 'mobx';

class ProductStore {
  products = [];
  // ... 其他与商品相关的状态和操作
  constructor() {
    makeAutoObservable(this);
  }
  // ...
}

export const productStore = new ProductStore();

其次,分离UI状态和领域状态。有些状态只与特定的UI组件相关,比如一个模态框的打开/关闭状态,一个表单的输入值。这些状态不一定需要提升到全局Store中,可以考虑在组件内部使用

useState
useLocalObservable
来管理。将真正的领域状态(如用户数据、商品列表)放在MobX Store中,保持Store的纯净和专注于核心业务逻辑。

再者,依赖注入是一个好实践。与其在每个组件或Store内部都

import
所有的Store实例,不如通过Context API或者一个统一的
rootStore
来提供这些Store。这样可以更灵活地管理依赖,也方便测试。

// stores/index.js (root store)
import { authStore } from './AuthStore';
import { productStore } from './ProductStore';

class RootStore {
  authStore = authStore;
  productStore = productStore;
  // ... 可以根据需要添加更多 Store
}

export const rootStore = new RootStore();

// 在 React 应用的顶层
import React from 'react';
import { rootStore } from './stores';

export const StoreContext = React.createContext(rootStore);

const App = () => (
  
    {/* 你的应用组件 */}
  
);

// 在组件中消费
import { useContext } from 'react';
import { StoreContext } from '../App';
import { observer } from 'mobx-react-lite';

const UserInfo = observer(() => {
  const { authStore } = useContext(StoreContext);
  if (authStore.isLoading) return 
Loading user...
; if (!authStore.isLoggedIn) return
Please log in.
; return
Welcome, {authStore.user.username}!
; });

最后,错误处理和异步操作。MobX对异步操作支持良好,通常结合

async/await
或MobX的
flow
(用于generator函数)来处理。在Store中封装异步操作时,务必考虑错误处理,设置加载状态(
isLoading
)和错误状态(
error
),让UI能够响应这些中间状态。这能提升用户体验,避免应用在异步操作失败时陷入不确定状态。

通过这些实践,我的项目Store结构变得清晰了很多,新功能开发时能快速定位到相关代码,也更容易进行单元测试。这让我有更多时间去关注业务逻辑本身,而不是被状态管理的复杂性所困扰。

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

558

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

436

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

756

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

479

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

534

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

1091

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

659

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

554

2023.09.20

c++ 根号
c++ 根号

本专题整合了c++根号相关教程,阅读专题下面的文章了解更多详细内容。

58

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3万人学习

Python 教程
Python 教程

共137课时 | 7.7万人学习

C 教程
C 教程

共75课时 | 4.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号