文章

跨端状态管理方案深度解析

跨端状态管理:Redux、MobX 等方案的原理与响应式思想,及在 RN/Flutter 的映射。 讲清选型要点,掌握后能答出「跨端状态管理怎么统一」。

跨端状态管理方案深度解析

一句话概括

Redux 和 MobX 的本质区别不是”函数式 vs 面向对象”,而是数据流是否显式可追踪——Redux 让你看到每一步变化,MobX 让你感觉不到在管理状态。

核心知识点

1. Redux:显式单向数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Redux 的核心:dispatch → reducer → new state → UI render
// 每一步都是显式的、可追溯的、可记录日志的

// 三个概念记牢:
// Store = 整个 App 的一棵状态树(单一数据源)
// Action = { type, payload },描述"发生了什么"
// Reducer = (state, action) => newState,纯函数,不能有副作用

// Redux Toolkit 让你不用手写 action type 常量和 switch-case:
import { createSlice } from '@reduxjs/toolkit';

const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [], total: 0 },
  reducers: {
    addItem(state, action) {
      state.items.push(action.payload);    // Immer 让你"可变"写法
      state.total += action.payload.price; // 但底层仍是不可变更新
    },
    clearCart(state) { state.items = []; state.total = 0; },
  },
});

2. MobX:隐式响应式追踪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// MobX 的核心:observable → computed → reaction
// 你只管改数据,框架自动追踪谁依赖了谁

import { makeAutoObservable } from 'mobx';

class CartStore {
  items = [];
  constructor() { makeAutoObservable(this); }

  get total() { return this.items.reduce((s, i) => s + i.price, 0); } // computed
  get isEmpty() { return this.items.length === 0; }

  addItem(item) { this.items.push(item); } // action,自动批处理
}

// 组件中用 observer 包裹,items 变了自动 re-render
// 你不需要 dispatch,不需要 selector,MobX 自己算依赖图

3. 中间件洋葱模型

1
dispatch(action) → mid1(before) → mid2(before) → reducer → mid2(after) → mid1(after)
1
2
3
4
5
6
7
8
9
10
11
12
// Redux 中间件本质是一个函数:
const loggerMiddleware = store => next => action => {
  console.log('dispatching', action);
  const result = next(action);  // 交给下一个中间件或 reducer
  console.log('next state', store.getState());
  return result;
};

// 跨端价值:不同平台注入不同中间件
// iOS/Android → NativeLogger 中间件,调用原生日志
// 小程序 → 自动 wx.setStorageSync 中间件
// Web → Redux DevTools 中间件

4. Immutable 结构共享

1
2
3
4
5
6
7
8
9
// Redux 要求返回全新对象,但全量深拷贝太贵
// 解法:结构共享 —— 没变的部分直接复用引用

const oldState = { user: { name: 'Tom' }, count: 1 };
const newState = { user: oldState.user, count: 2 }; // user 引用没变!
// Redux 的 useSelector 用 === 比较,user 引用相同就跳过重渲染

// RTK 内置 Immer,你写"可变"代码,它生成不可变结果
// 这就是为什么 createSlice 的 reducer 里能写 state.value += 1

5. Selector 缓存机制

1
2
3
4
5
6
7
8
9
10
11
12
13
// createSelector 只在依赖变化时重新计算
import { createSelector } from '@reduxjs/toolkit';

const selectItems = state => state.cart.items;
const selectFilter = state => state.cart.filter;

const selectFilteredItems = createSelector(
  [selectItems, selectFilter],
  (items, filter) => {  // items 或 filter 没变就返回缓存值,不重新算
    console.log('重新计算'); // 只在必要时打印
    return items.filter(i => i.category === filter);
  }
);

其实你每天都在用

  • Redux 的 reducer:跟 Array.reduce((acc, cur) => ...) 完全一样的签名,(累积状态, 当前操作) => 下一个状态
  • MobX 的 makeAutoObservable:相当于 Vue 3 的 reactive(),你改了对象属性,框架自动追踪
  • useSelector:类似 React 的 useContext,但多了一层 === 比较来判断要不要 rerender
  • Redux DevTools:能回放、快进、跳过任意 action——这不是魔法,是 Redux 把每一步 state 都存了下来
  • action 批处理:runInAction(() => { a=1; b=2; }) 内部改 100 次也只触发一次 re-render,跟 Vue 的 nextTick 批量更新思路一致

常见误解(FAQ)

❌ 误区一:”Redux 已经不流行了,应该用 Zustand/Jotai”

Redux Toolkit 和传统手写 Redux 是完全不同的体验。RTK 内置 Immer、createAsyncThunk、RTK Query,样板代码量已经接近 Zustand。Redux 的独特优势在于中间件生态和 DevTools 时间旅行调试——这两个在大团队项目中仍然是杀手级特性。

❌ 误区二:”MobX 不适合大型项目”

MobX 在大型项目中的真正挑战不是性能,而是可预测性。当 20 个 observer 组件订阅了同一个 observable 对象的不同属性,一次修改到底会触发几个组件更新?这需要你理解整个依赖图。Redux 则相反——一条 dispatch 链路清清楚楚。所以”适合大项目”指的不是规模,而是你是否需要显式的可追溯性。

❌ 误区三:”MobX 不用 selector,性能会差”

MobX 的依赖粒度比 selector 更细——selector 通常是”过滤整个列表”,MobX 可以精确到列表第 3 项的 name 属性变了才触发对应组件。只是在深层嵌套场景下,这个依赖图的维护本身有开销。Redux 的 selector 在有 createSelector 缓存时通常更可预测。

一句话总结

选 Redux 还是 MobX 的终极问题不是技术偏好,而是一年后你还能不能看懂自己的状态流转——Redux 让你反复读日志复盘,MobX 让你写得飞快但事后难追溯。

本文由作者按照 CC BY 4.0 进行授权