当前位置: 首页 > news >正文

MobX与响应式编程实践

响应式编程基础

MobX 是一个简洁高效的状态管理库,它通过响应式编程范式实现了状态与UI的自动同步。核心优势在于其简单直观的API和出色的开发体验。

核心概念

// 定义可观察状态
import { makeObservable, observable, action, computed } from "mobx";class TodoStore {constructor() {makeObservable(this, {todos: observable,unfinishedTodoCount: computed,addTodo: action,toggleTodo: action});}todos = [];get unfinishedTodoCount() {return this.todos.filter(todo => !todo.finished).length;}addTodo(title) {this.todos.push({ id: Date.now(), title, finished: false });}toggleTodo(id) {const todo = this.todos.find(todo => todo.id === id);if (todo) {todo.finished = !todo.finished;}}
}

MobX响应式原理解析

自动依赖追踪机制

MobX的核心魔力在于其精确的依赖追踪系统。当组件渲染或计算属性执行时,MobX会自动收集它们读取的可观察状态,并在这些状态发生变化时重新计算和渲染。

import { makeAutoObservable, autorun } from "mobx";class TemperatureStore {constructor() {makeAutoObservable(this);}temperatureCelsius = 25;get temperatureFahrenheit() {console.log("Computing Fahrenheit");return this.temperatureCelsius * 1.8 + 32;}setTemperatureCelsius(value) {this.temperatureCelsius = value;}
}const temperature = new TemperatureStore();// 创建自动执行的反应
const dispose = autorun(() => {console.log(`Temperature: ${temperature.temperatureFahrenheit}°F`);
});// 当温度变化时,自动重新计算并打印
temperature.setTemperatureCelsius(30);// 停止自动执行
dispose();

实现原理:Proxy与依赖图

MobX 6使用JavaScript的Proxy特性实现数据劫持,当读取observable属性时,MobX会将当前执行上下文(如autorun或组件渲染)注册为该属性的订阅者。通过维护一个动态依赖图,MobX确保只有真正依赖变化数据的计算才会重新执行。
Ran tool

与Redux的深度对比分析

思维模型与状态管理理念

特性MobXRedux
状态模型多个独立状态树,OOP风格单一状态树,函数式风格
修改方式直接修改(通过action)不可变更新(reducer)
模板代码少,直观较多,规范化
学习曲线平缓陡峭
调试能力中等优秀(时间旅行等)
适用规模小到中型应用中到大型应用

代码对比:同一功能两种实现

MobX实现:

// store.js
import { makeAutoObservable } from "mobx";class CounterStore {count = 0;constructor() {makeAutoObservable(this);}increment() {this.count++;}decrement() {this.count--;}reset() {this.count = 0;}
}export const counterStore = new CounterStore();// Counter.jsx
import React from "react";
import { observer } from "mobx-react-lite";
import { counterStore } from "./store";const Counter = observer(() => {return (<div><h2>Count: {counterStore.count}</h2><button onClick={() => counterStore.increment()}>+</button><button onClick={() => counterStore.decrement()}>-</button><button onClick={() => counterStore.reset()}>Reset</button></div>);
});export default Counter;

Redux实现:

// store.js
import { createSlice, configureStore } from "@reduxjs/toolkit";const counterSlice = createSlice({name: "counter",initialState: { count: 0 },reducers: {increment: state => {state.count++;},decrement: state => {state.count--;},reset: state => {state.count = 0;}}
});export const { increment, decrement, reset } = counterSlice.actions;
export const store = configureStore({reducer: {counter: counterSlice.reducer}
});// Counter.jsx
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement, reset } from "./store";const Counter = () => {const count = useSelector(state => state.counter.count);const dispatch = useDispatch();return (<div><h2>Count: {count}</h2><button onClick={() => dispatch(increment())}>+</button><button onClick={() => dispatch(decrement())}>-</button><button onClick={() => dispatch(reset())}>Reset</button></div>);
};export default Counter;

性能比较

MobX通过精确的依赖追踪和细粒度更新提供出色的默认性能,而Redux需要开发者手动优化(如memoization)以避免不必要的重渲染。

高级MobX模式与技巧

组合式Store设计

import { makeAutoObservable } from "mobx";// 领域store: 用户
class UserStore {user = null;loading = false;error = null;constructor(rootStore) {makeAutoObservable(this);this.rootStore = rootStore;}async login(credentials) {this.loading = true;this.error = null;try {const response = await fetch("/api/login", {method: "POST",headers: { "Content-Type": "application/json" },body: JSON.stringify(credentials)});if (!response.ok) throw new Error("Login failed");this.user = await response.json();} catch (err) {this.error = err.message;} finally {this.loading = false;}}logout() {this.user = null;// 可以访问其他storethis.rootStore.cartStore.clearCart();}
}// 领域store: 购物车
class CartStore {items = [];constructor(rootStore) {makeAutoObservable(this);this.rootStore = rootStore;}addItem(item) {this.items.push(item);}clearCart() {this.items = [];}get totalPrice() {return this.items.reduce((sum, item) => sum + item.price, 0);}get isUserLoggedIn() {return !!this.rootStore.userStore.user;}
}// 根store
class RootStore {constructor() {this.userStore = new UserStore(this);this.cartStore = new CartStore(this);}
}export const rootStore = new RootStore();

避免响应式陷阱

1. 避免在渲染过程中修改状态
// ❌ 错误:在render过程中修改状态
const TodoList = observer(() => {if (todoStore.todos.length === 0) {todoStore.initializeTodos(); // 会导致无限循环!}return (<ul>{todoStore.todos.map(todo => <li key={todo.id}>{todo.title}</li>)}</ul>);
});// ✅ 正确:使用React的生命周期或效果
const TodoList = observer(() => {const { todos, initializeTodos } = todoStore;React.useEffect(() => {if (todos.length === 0) {initializeTodos();}}, [todos.length, initializeTodos]);return (<ul>{todos.map(todo => <li key={todo.id}>{todo.title}</li>)}</ul>);
});
2. 确保组件只观察它需要的数据
// ❌ 低效:整个组件对store中的所有变化都做出反应
const ProfilePage = observer(() => {const { userStore } = useStores();return (<div><h1>{userStore.user.name}</h1><span>{userStore.user.email}</span><ActivityFeed activities={userStore.activities} /></div>);
});// ✅ 优化:拆分组件,精确控制观察范围
const ProfileHeader = observer(() => {const { userStore } = useStores();const { name, email } = userStore.user;return (<div><h1>{name}</h1><span>{email}</span></div>);
});const ActivitySection = observer(() => {const { userStore } = useStores();return <ActivityFeed activities={userStore.activities} />;
});const ProfilePage = () => (<div><ProfileHeader /><ActivitySection /></div>
);
3. 使用runInAction处理异步操作
import { makeAutoObservable, runInAction } from "mobx";class DataStore {data = [];loading = false;error = null;constructor() {makeAutoObservable(this);}async fetchData() {this.loading = true;this.error = null;try {const response = await fetch("/api/data");const result = await response.json();// 使用runInAction将多个状态更新包装在一个事务中runInAction(() => {this.data = result;this.loading = false;});} catch (error) {runInAction(() => {this.error = error.message;this.loading = false;});}}
}

案例:构建高性能复杂表单

需求与挑战

开发一个复杂的动态表单,包含多级嵌套字段、条件验证和即时计算。表单需要高性能且响应式,同时保持良好的可维护性。

MobX表单实现

import { makeAutoObservable, reaction } from "mobx";class FormField {value = "";touched = false;error = null;constructor(initialValue = "", validations = []) {this.value = initialValue;this.validations = validations;makeAutoObservable(this);}setValue(newValue) {this.value = newValue;this.touched = true;this.validate();}validate() {for (const validation of this.validations) {const error = validation(this.value);if (error) {this.error = error;return false;}}this.error = null;return true;}reset() {this.value = "";this.touched = false;this.error = null;}
}class FormStore {constructor() {makeAutoObservable(this);// 设置字段间的依赖关系reaction(() => this.shippingAddress.sameAsBilling.value,sameAsBilling => {if (sameAsBilling) {this.shippingAddress.street.setValue(this.billingAddress.street.value);this.shippingAddress.city.setValue(this.billingAddress.city.value);this.shippingAddress.zipCode.setValue(this.billingAddress.zipCode.value);}});}// 使用嵌套结构组织表单字段personalInfo = {firstName: new FormField("", [value => !value ? "First name is required" : null]),lastName: new FormField("", [value => !value ? "Last name is required" : null]),email: new FormField("", [value => !value ? "Email is required" : null,value => !/\S+@\S+\.\S+/.test(value) ? "Invalid email format" : null])};billingAddress = {street: new FormField("", [value => !value ? "Street is required" : null]),city: new FormField("", [value => !value ? "City is required" : null]),zipCode: new FormField("", [value => !value ? "ZIP code is required" : null,value => !/^\d{5}(-\d{4})?$/.test(value) ? "Invalid ZIP code" : null])};shippingAddress = {sameAsBilling: new FormField(false),street: new FormField(""),city: new FormField(""),zipCode: new FormField("")};payment = {cardNumber: new FormField("", [value => !value ? "Card number is required" : null,value => !/^\d{16}$/.test(value.replace(/\s/g, "")) ? "Invalid card number" : null]),expiryDate: new FormField("", [value => !value ? "Expiry date is required" : null,value => !/^(0[1-9]|1[0-2])\/\d{2}$/.test(value) ? "Invalid format (MM/YY)" : null]),cvv: new FormField("", [value => !value ? "CVV is required" : null,value => !/^\d{3,4}$/.test(value) ? "Invalid CVV" : null])};get isFormValid() {// 递归验证所有表单字段const validateSection = section => {return Object.values(section).every(field => {if (field instanceof FormField) {return field.validate();} else if (typeof field === "object") {return validateSection(field);}return true;});};return validateSection(this);}submit() {if (!this.isFormValid) {console.error("Form has validation errors");return false;}// 收集表单数据const formData = {personalInfo: {firstName: this.personalInfo.firstName.value,lastName: this.personalInfo.lastName.value,email: this.personalInfo.email.value},billingAddress: {street: this.billingAddress.street.value,city: this.billingAddress.city.value,zipCode: this.billingAddress.zipCode.value},shippingAddress: this.shippingAddress.sameAsBilling.value? {sameAsBilling: true,...this.billingAddress}: {sameAsBilling: false,street: this.shippingAddress.street.value,city: this.shippingAddress.city.value,zipCode: this.shippingAddress.zipCode.value},payment: {cardNumber: this.payment.cardNumber.value,expiryDate: this.payment.expiryDate.value,cvv: this.payment.cvv.value}};console.log("Form submitted:", formData);return true;}reset() {const resetSection = section => {Object.values(section).forEach(field => {if (field instanceof FormField) {field.reset();} else if (typeof field === "object") {resetSection(field);}});};resetSection(this);}
}export const formStore = new FormStore();

表单组件实现

import React from "react";
import { observer } from "mobx-react-lite";
import { formStore } from "./formStore";// 字段组件
const Field = observer(({ field, label, type = "text" }) => {return (<div className="form-field"><label>{label}</label><inputtype={type}value={field.value}onChange={e => field.setValue(e.target.value)}className={field.error && field.touched ? "error" : ""}/>{field.error && field.touched && (<div className="error-message">{field.error}</div>)}</div>);
});// 复选框组件
const Checkbox = observer(({ field, label }) => {return (<div className="form-checkbox"><inputtype="checkbox"checked={field.value}onChange={e => field.setValue(e.target.checked)}id={`checkbox-${label}`}/><label htmlFor={`checkbox-${label}`}>{label}</label></div>);
});// 表单组件
const CheckoutForm = observer(() => {const handleSubmit = e => {e.preventDefault();if (formStore.submit()) {alert("Order placed successfully!");}};return (<form onSubmit={handleSubmit} className="checkout-form"><h2>Personal Information</h2><div className="form-section"><Fieldfield={formStore.personalInfo.firstName}label="First Name"/><Fieldfield={formStore.personalInfo.lastName}label="Last Name"/><Fieldfield={formStore.personalInfo.email}label="Email"type="email"/></div><h2>Billing Address</h2><div className="form-section"><Fieldfield={formStore.billingAddress.street}label="Street Address"/><Fieldfield={formStore.billingAddress.city}label="City"/><Fieldfield={formStore.billingAddress.zipCode}label="ZIP Code"/></div><h2>Shipping Address</h2><div className="form-section"><Checkboxfield={formStore.shippingAddress.sameAsBilling}label="Same as Billing Address"/>{!formStore.shippingAddress.sameAsBilling.value && (<><Fieldfield={formStore.shippingAddress.street}label="Street Address"/><Fieldfield={formStore.shippingAddress.city}label="City"/><Fieldfield={formStore.shippingAddress.zipCode}label="ZIP Code"/></>)}</div><h2>Payment Information</h2><div className="form-section"><Fieldfield={formStore.payment.cardNumber}label="Card Number"/><Fieldfield={formStore.payment.expiryDate}label="Expiry Date (MM/YY)"/><Fieldfield={formStore.payment.cvv}label="CVV"type="password"/></div><div className="form-actions"><button type="button" onClick={() => formStore.reset()}>Reset</button><buttontype="submit"disabled={!formStore.isFormValid}>Place Order</button></div></form>);
});export default CheckoutForm;

扩展与集成

与React hooks结合

// useStore.js - 创建自定义hook访问stores
import React from "react";
import { rootStore } from "./stores";// React context用于提供stores
const StoreContext = React.createContext(null);// Provider组件
export const StoreProvider = ({ children }) => {return (<StoreContext.Provider value={rootStore}>{children}</StoreContext.Provider>);
};// 自定义hook用于访问stores
export const useStores = () => {const store = React.useContext(StoreContext);if (!store) {throw new Error("useStores must be used within a StoreProvider");}return store;
};// 方便访问特定store的hooks
export const useUserStore = () => useStores().userStore;
export const useCartStore = () => useStores().cartStore;

配合TypeScript提升类型安全

// store.ts
import { makeAutoObservable } from "mobx";interface Todo {id: number;title: string;completed: boolean;
}class TodoStore {todos: Todo[] = [];constructor() {makeAutoObservable(this);}addTodo(title: string): void {this.todos.push({id: Date.now(),title,completed: false});}toggleTodo(id: number): void {const todo = this.todos.find(todo => todo.id === id);if (todo) {todo.completed = !todo.completed;}}removeTodo(id: number): void {this.todos = this.todos.filter(todo => todo.id !== id);}get completedCount(): number {return this.todos.filter(todo => todo.completed).length;}get remainingCount(): number {return this.todos.length - this.completedCount;}
}export const todoStore = new TodoStore();

性能优化

1. 精细组件粒度

将大型组件拆分为小型组件,每个组件只观察它所需的状态部分,避免不必要的重渲染。

2. 使用computed避免重复计算

class ShoppingCartStore {items = [];taxRate = 0.08;constructor() {makeAutoObservable(this);}// 使用computed缓存计算结果get subtotal() {console.log("Computing subtotal"); // 只有items变化时才会执行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;}
}

3. 优化大型列表渲染

import { observer } from "mobx-react-lite";
import { useCallback } from "react";// 优化大型列表:单独的列表项组件
const TodoItem = observer(({ todo, onToggle, onDelete }) => {console.log(`Rendering TodoItem: ${todo.id}`);return (<li><inputtype="checkbox"checked={todo.completed}onChange={onToggle}/><span style={{ textDecoration: todo.completed ? "line-through" : "none" }}>{todo.title}</span><button onClick={onDelete}>Delete</button></li>);
});// 列表容器组件
const TodoList = observer(({ todoStore }) => {console.log("Rendering TodoList");const handleToggle = useCallback((id) => {todoStore.toggleTodo(id);}, [todoStore]);const handleDelete = useCallback((id) => {todoStore.removeTodo(id);}, [todoStore]);return (<div><h2>Todo List ({todoStore.remainingCount} remaining)</h2><ul>{todoStore.todos.map(todo => (<TodoItemkey={todo.id}todo={todo}onToggle={() => handleToggle(todo.id)}onDelete={() => handleDelete(todo.id)}/>))}</ul></div>);
});

总结与思考

何时选择MobX

  • 小到中型应用,需要快速开发与简单状态管理
  • 倾向于面向对象编程风格
  • 项目需要低样板代码和直观API
  • 团队来自传统OOP背景,学习曲线需要平缓

何时选择Redux

  • 大型应用,需要严格的状态管理和可预测性
  • 需要时间旅行调试和状态历史记录
  • 团队更喜欢函数式编程范式
  • 项目需要一致的状态更新模式和中间件生态

MobX总结

  1. 保持store简单:将复杂逻辑分解到多个专用store
  2. 使用action修改状态:保持状态变更的可追踪性
  3. 合理使用computed:优化派生计算,避免重复计算
  4. 细化组件粒度:确保组件只订阅它需要的状态
  5. 运用TypeScript:获得编译时类型安全和更好的开发体验
  6. 结合React hooks:通过自定义hooks优雅地访问store
  7. 避免响应式陷阱:不在render中修改状态,谨慎处理异步操作

无论选择哪种状态管理方案,关键是理解其底层原理和设计思想,这样才能充分发挥其优势,创建高性能、可维护的前端应用。

参考资源

官方文档与指南

  • MobX 官方文档 - 权威的API参考和概念解释
  • MobX GitHub仓库 - 源代码和最新更新
  • MobX 常见问题解答 - 解答常见疑问和陷阱

进阶学习资源

  • 深入理解MobX与React - Michel Weststrate(MobX作者)的深度解析
  • MobX 与 React: 完整指南 - Packt出版的完整指南书籍
  • 使用MobX进行状态管理 - Egghead.io上的视频教程

工具与扩展

  • MobX-React - React与MobX的官方绑定
  • MobX-State-Tree - 基于MobX的可组合状态容器
  • MobX DevTools - 调试MobX应用的开发者工具

社区资源

  • Awesome MobX - 精选MobX相关资源列表

  • React+MobX最佳实践 - Robin Wieruch的完整指南

性能优化指南

  • MobX性能优化最佳实践 - 官方推荐的性能优化技巧
  • 优化React与MobX应用 - 深入的性能优化指南

比较分析

  • Redux vs MobX: 何时使用哪一个? - 深入比较两种状态管理方案
  • 现代React状态管理比较 - 涵盖多种状态管理方案

响应式编程扩展阅读

  • 响应式编程介绍 - André Staltz的经典文章
  • 深入理解JavaScript响应式原理 - 关于JavaScript响应式实现的技术文章

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

相关文章:

  • MySQL:分区的基本使用
  • 外贸网站服务器选择Siteground还是Hostinger,哪个更好?
  • 【C/C++】STL实现版本为什么比手写版本高?
  • 在Mathematica中使用Newton-Raphson迭代绘制一个花脸
  • 跳转指令四维全解:从【call/jmp 】的时空法则到内存迷宫导航术
  • 跳跃游戏 dp还是线段树优化
  • 在ubuntu等linux系统上申请https证书
  • OneNet + openssl + MTLL
  • GoC指令测试卷 A
  • 十一、【ESP32开发全栈指南: TCP通信服务端】
  • 零基础入门PCB设计 强化篇 第六章(实验——USB拓展坞PCB绘制)
  • Python爬虫-爬取各省份各年份高考分数线数据,进行数据分析
  • 物联网智慧医院建设方案(PPT)
  • 服务器新建用户无法使用conda
  • [HCTF 2018]admin 1
  • vue3单独封装表单校验函数
  • 基于算法竞赛的c++编程(21)cin,scanf性能差距和优化
  • 题海拾贝:P1091 [NOIP 2004 提高组] 合唱队形
  • 总结html标签之button标签
  • Global Security Markets 第 10 章衍生品知识点总结​
  • 阿里云ecs做淘客网站/品牌网络营销策划书
  • 建设充值网站多钱/西安百度框架户
  • 网站设置在哪里找到/广告推广投放平台
  • 企业做响应式网站好吗/网站seo推广计划
  • 济宁网站网站建设/搜索引擎推广实训
  • 江苏省建设厅网站证件查询/河北百度seo