Redux、MobX、Recoil、Zustand 、Jotai 对比

Redux、MobX、Recoil、Zustand 、Jotai 对比
Photo by Jason Dent / Unsplash

作为一名资深的 React 大型应用开发者,我将从架构、设计模式和原理最小实现代码等方面对比 Redux、MobX、Recoil、Zustand 和 Jotai 这几个状态管理库。

Redux:

  • 架构:Redux 遵循单一数据源原则,整个应用的状态存储在一个单一的 store 中。数据流是单向的,通过 dispatch action 来触发状态更新,reducer 函数根据 action 类型和负载来计算新的状态。
  • 设计模式:Redux 采用了发布-订阅模式,store 作为发布者,组件作为订阅者。当 store 中的状态发生变化时,组件会收到通知并重新渲染。
  • 原理最小实现代码
function createStore(reducer) {
  let state = reducer(undefined, {});
  const subscribers = [];

  function dispatch(action) {
    state = reducer(state, action);
    subscribers.forEach(subscriber => subscriber());
  }

  function subscribe(callback) {
    subscribers.push(callback);
  }

  function getState() {
    return state;
  }

  return { dispatch, subscribe, getState };
}

MobX:

  • 架构:MobX 采用响应式编程的思想,通过可观察对象 (observable) 来存储状态,并自动追踪状态的变化。当可观察对象发生变化时,依赖于它的计算属性 (computed) 和反应 (reaction) 会自动更新。
  • 设计模式:MobX 使用了观察者模式,可观察对象是被观察者,计算属性和反应是观察者。当可观察对象发生变化时,观察者会得到通知并执行相应的操作。
  • 原理最小实现代码:
function createStore(reducer) {
  let state = reducer(undefined, {});
  const subscribers = [];

  function dispatch(action) {
    state = reducer(state, action);
    subscribers.forEach(subscriber => subscriber());
  }

  function subscribe(callback) {
    subscribers.push(callback);
  }

  function getState() {
    return state;
  }

  return { dispatch, subscribe, getState };
}

MobX:

  • 架构:MobX 采用响应式编程的思想,通过可观察对象 (observable) 来存储状态,并自动追踪状态的变化。当可观察对象发生变化时,依赖于它的计算属性 (computed) 和反应 (reaction) 会自动更新。
  • 设计模式:MobX 使用了观察者模式,可观察对象是被观察者,计算属性和反应是观察者。当可观察对象发生变化时,观察者会得到通知并执行相应的操作。
  • 原理最小实现代码:
function observable(obj) {
  const observers = new Set();

  return new Proxy(obj, {
    set(target, key, value) {
      target[key] = value;
      observers.forEach(observer => observer());
      return true;
    },
    get(target, key) {
      if (typeof target[key] === 'function') {
        return target[key].bind(target);
      }
      return target[key];
    },
  });
}

function autorun(reaction) {
  reaction();
}

Recoil:

  • 架构:Recoil 基于原子化状态 (atom) 和派生状态 (selector) 的概念。原子表示可独立更新的状态单元,选择器表示根据原子或其他选择器计算得出的状态。
  • 设计模式:Recoil 采用了依赖注入的思想,通过 Provider 组件将 store 注入到组件树中,组件通过 useRecoilState 等 Hook 来访问和更新状态。
  • 原理最小实现代码:
function atom(initialValue) {
  let value = initialValue;
  const subscribers = new Set();

  function get() {
    return value;
  }

  function set(newValue) {
    value = newValue;
    subscribers.forEach(subscriber => subscriber());
  }

  function subscribe(callback) {
    subscribers.add(callback);
    return () => subscribers.delete(callback);
  }

  return { get, set, subscribe };
}

function useRecoilState(atom) {
  const [, forceUpdate] = useState(0);

  useEffect(() => {
    const unsubscribe = atom.subscribe(() => forceUpdate(n => n + 1));
    return unsubscribe;
  }, [atom]);

  return [atom.get(), atom.set];
}

Zustand:

  • 架构:Zustand 基于 Hook 的概念,将状态和操作状态的方法集中在一个 store 中。通过自定义 Hook 来创建和访问 store。
  • 设计模式:Zustand 使用了单例模式,整个应用共享一个全局的 store 实例。通过 useStore 这个自定义 Hook 来访问 store 中的状态和方法。
  • 原理最小实现代码:
function createStore(createState) {
  let state;
  const subscribers = new Set();

  function setState(partial) {
    state = { ...state, ...partial };
    subscribers.forEach(subscriber => subscriber());
  }

  function getState() {
    return state;
  }

  function subscribe(callback) {
    subscribers.add(callback);
    return () => subscribers.delete(callback);
  }

  state = createState(setState, getState);

  return { getState, subscribe };
}

function useStore(selector) {
  const store = useMemo(() => createStore(createState), []);
  const [, forceUpdate] = useState(0);

  useEffect(() => {
    const unsubscribe = store.subscribe(() => forceUpdate(n => n + 1));
    return unsubscribe;
  }, [store]);

  return selector(store.getState());
}

Jotai:

  • 架构:Jotai 也是基于原子化概念,每个原子表示一个独立的状态单元。通过 Provider 组件将原子注入到组件树中,组件通过 useAtom 等 Hook 来访问和更新原子。
  • 设计模式:Jotai 采用了依赖注入和发布-订阅模式。Provider 组件负责注入原子,原子内部维护了订阅者列表,当原子值发生变化时通知订阅者。
  • 原理最小实现代码:
function atom(initialValue) {
  let value = initialValue;
  const subscribers = new Set();

  function read() {
    return value;
  }

  function write(newValue) {
    value = newValue;
    subscribers.forEach(subscriber => subscriber());
  }

  function subscribe(callback) {
    subscribers.add(callback);
    return () => subscribers.delete(callback);
  }

  return { read, write, subscribe };
}

function useAtom(atom) {
  const [, forceUpdate] = useState(0);

  useEffect(() => {
    const unsubscribe = atom.subscribe(() => forceUpdate(n => n + 1));
    return unsubscribe;
  }, [atom]);

  return [atom.read(), atom.write];
}

以上是对 Redux、MobX、Recoil、Zustand 和 Jotai 这几个状态管理库的架构、设计模式和原理最小实现代码的简要对比。每个库都有其独特的特点和适用场景:

  • Redux 适用于大型应用,特别是需要进行复杂状态管理和中间件处理的场景。
  • MobX 适用于中小型应用,特别是需要进行频繁的状态更新和副作用处理的场景。
  • Recoil 适用于中等规模的应用,特别是需要进行状态组合和异步数据流处理的场景。
  • Zustand 适用于小型到中型应用,特别是需要快速开发和简洁 API 的场景。
  • Jotai 适用于各种规模的应用,特别是对性能要求较高的场景。

在实际开发中,我们需要根据具体的业务需求和团队的技术背景来选择适合的状态管理方案。同时,不同的状态管理库也可以互补,可以在同一个项目中针对不同的模块和功能选择最合适的库。

Read more

Vue.js异步更新与nextTick机制深度解析(上篇)

Vue.js异步更新与nextTick机制深度解析(上篇)

本文目标 学完本文,你将能够: * 理解Vue.js为什么采用异步更新策略 * 掌握更新队列的设计思想和实现机制 * 深入理解Event Loop在Vue中的应用 * 了解nextTick的多种实现方式 系列导航 上一篇: Diff算法深度剖析 | 下一篇: Vue.js异步更新与nextTick机制(下篇) | 组件系统架构 引言:为什么DOM更新是异步的? 在Vue.js开发中,你可能遇到过这样的场景: // 场景1:连续修改数据 export default { data() { return { count: 0 } }, methods: { increment() { // 如果每次修改都立即更新DOM,会触发3次DOM更新 this.count++ // 触发一次? this.count++ // 触发一次? this.count++ // 触发一次? // 实际上:Vue只会触发一次DOM更新!

Vue.js组件系统架构深度解析

本文目标 学完本文,你将能够: * 理解Vue.js组件从创建到销毁的完整生命周期 * 掌握组件实例化和初始化的内部流程 * 深入理解父子组件通信的底层机制 * 学会实现完整的插槽系统(包括作用域插槽) * 掌握动态组件和异步组件的实现原理 * 应用组件级别的性能优化技巧 系列导航 上一篇: 异步更新与nextTick(下篇) | 下一篇: 状态管理模式 引言:组件是如何工作的? 在Vue.js开发中,我们每天都在使用组件。但你是否想过: // 当我们这样定义一个组件 const MyComponent = { data() { return { count: 0 } }, template: '<button @click="count++">{{ count }}</button>' } // 并使用它时 new Vue({ components: { MyComponent }, template:

Vue.js状态管理模式:构建可扩展的应用架构

本文目标 学完本文,你将能够: * 理解为什么大型应用需要状态管理 * 掌握Vuex的核心设计模式和实现原理 * 实现一个简化版的状态管理库 * 理解模块化和命名空间的设计思想 * 掌握大型应用的状态管理最佳实践 * 了解现代状态管理方案的演进 系列导航 上一篇: 组件系统架构 | 下一篇: 性能优化实践 1. 为什么需要状态管理? 1.1 组件通信的困境 在大型Vue.js应用中,组件间的通信会变得异常复杂: // 问题场景:多层级组件的状态共享 // GrandParent.vue <template> <Parent :user="user" @update-user="updateUser" /> </template> // Parent.vue <template> <Child

Vue.js依赖收集与追踪机制深度剖析

本文目标 学完本文,你将能够: * 理解Vue.js如何精确知道哪些组件需要更新 * 掌握Dep、Watcher、Observer三大核心类的协作机制 * 深入理解依赖收集的时机和完整过程 * 能够手写一个完整的依赖收集系统 * 解决实际开发中的依赖追踪问题 系列导航 上一篇: 响应式系统核心原理 | 下一篇: Virtual DOM实现详解 引言:为什么Vue知道哪些组件需要更新? 在使用Vue.js时,你是否想过这样一个问题:当我们修改一个数据时,Vue是如何精确地知道哪些组件用到了这个数据,并只更新这些组件的? // 假设有这样的场景 const app = new Vue({ data: { user: { name: 'John', age: 25 } } }); // 组件A只用到了user.name // 组件B只用到了user.age // 组件C同时用到了name和age // 当我们修改user.name时 app.user.name = 'Jane&