这么多状态管理库,你会选哪个?

背景

随着前端技术的发展,Angular、React 和 Vue 等前端框架成为主流,而这些框架特点是组件化数据驱动

前端应用程序通常会随着业务的复杂性的增加而变得庞大。在这样的应用中,组件之间状态共享和管理可能会变得非常困难。所以我们就需要一种状态管理方式,以下的状态管理库就应运而生。

那么面对这么多状态管理库,我们应该怎么选择呢?做出选择之前,我们应该去了解一下它们。

Flux

Tips:Flux 项目已存档,不会再做任何更改。

简介

Flux(前端状态管理的鼻祖) 是 Facebook 用于构建客户端 Web 应用程序的应用程序架构。它通过利用单向数据流补充了 React 的可组合视图组件。它更像是一种模式,而不是一个正式的框架

这么多状态管理库,你会选哪个?

Flux 模式的核心

单向数据流,即数据的变化只能通过 Actions(动作) -> Dispatcher(调度器) -> Stores(数据仓库) -> Views(视图) 的顺序进行。(可以使状态的变化易于追踪和调试)。

Actions 是描述应用中发生的事件的对象。它们是应用中唯一能够触发状态变化的来源。Actions 包含了一个标识符(type)和一些相关的数据,用于描述发生的事件。

Dispatcher 是 Flux 架构中的中央调度器。它负责接收所有的 Action,然后将它们分发给注册在其上的所有 Store。Dispatcher 确保 Actions 被有序地传递给 Stores,避免了多个 Action 导致状态不一致的问题。

Stores 是应用程序中存储状态和逻辑的地方。它们用于管理应用程序的状态,响应 Dispatcher 分发的 Actions,并触发相应的状态变化。每个 Store 通常管理应用程序的某一部分状态。

Views 是用户界面的表示,它们从 Stores 获取数据并更新界面以反映当前应用程序的状态。Views 可以发送 Actions 到 Dispatcher,从而触发状态的变化。

使用方法

创建一个简单的任务管理应用。

// TaskActions.js
import { Dispatcher } from 'flux';

const dispatcher = new Dispatcher();

// 添加新任务
export const addTask = (task) => {
  dispatcher.dispatch({
    type: 'ADD_TASK',
    task: task
  });
};

// 标记任务为已完成
export const completeTask = (taskId) => {
  dispatcher.dispatch({
    type: 'COMPLETE_TASK',
    taskId: taskId
  });
};

// 删除任务
export const deleteTask = (taskId) => {
  dispatcher.dispatch({
    type: 'DELETE_TASK',
    taskId: taskId
  });
};
// TaskStore.js
import { EventEmitter } from 'events';
import dispatcher from './TaskActions';

const taskStore = new EventEmitter();

let tasks = [];

dispatcher.register((action) => {
  switch (action.type) {
    case 'ADD_TASK':
      tasks.push({
        id: tasks.length + 1,
        text: action.task,
        completed: false
      });
      taskStore.emit('change');
      break;
    case 'COMPLETE_TASK':
      tasks[action.taskId - 1].completed = true;
      taskStore.emit('change');
      break;
    case 'DELETE_TASK':
      tasks = tasks.filter(item => item.id !== action.taskId);
      taskStore.emit('change');
      break;
  }
});

export const getTasks = () => tasks;

taskStore.on('change', () => {
  console.log('Task Store changed');
});

export default taskStore;
// TaskList.js
import React, { useState, useEffect } from 'react';
import { addTask, completeTask, deleteTask } from './TaskActions';
import taskStore from './TaskStore';

const TaskList = () => {
  const [tasks, setTasks] = useState([]);

  useEffect(() => {
    setTasks(taskStore.getTasks());

    const handleChange = () => {
      setTasks(taskStore.getTasks());
    };

    taskStore.on('change', handleChange);

    return () => {
      taskStore.off('change', handleChange);
    };
  }, []);

  const handleAddTask = (task) => {
    addTask(task);
  };

  const handleCompleteTask = (taskId) => {
    completeTask(taskId);
  };
  
  const handleDeleteTask = (taskId) => {
    deleteTask(taskId);
  };

  return (
    <div>
      <ul>
        {tasks.map(task => (
          <li key={task.id}>
            {task.text}
            {!task.completed && <button onClick={() => handleCompleteTask(task.id)}>Complete</button>}
            <button onClick={() => handleDeleteTask(task.id)}>Delete</button>
          </li>
        ))}
      </ul>
      <input type="text" placeholder="New task" onKeyDown={(e) => e.key === 'Enter' && handleAddTask(e.target.value)} />
    </div>
  );
};

export default TaskList;

小结

Flux 作为鼻祖,应该了解一下,不推荐使用,因为已经被淘汰了,官方也已经停止维护了。

淘汰理由:

  1. 复杂性和冗余代码: Flux 的原始实现中存在大量的 boilerplate 代码,需要创建多个 Store 和 Actions,使得代码变得繁琐和复杂。一些现代的状态管理库(如 Redux)在简化了 Flux 的实现同时保持了其核心思想,提高了开发效率。
  2. 更简单的替代方案: 随着前端生态的发展,出现了更简单和轻量级的状态管理库,如 Redux、MobX、Vuex 等。这些库在实现上保持了单向数据流的思想,同时减少了冗余的代码。
  3. 更直观的 API: Flux 的概念可能对一些开发者来说比较抽象,而一些现代的状态管理库提供了更直观、易理解的 API。这有助于降低学习曲线,提高开发者的使用体验。

Flux 的核心思想在一定程度上依然存在,并且影响了后续的状态管理库的设计。然而,直接使用原始的 Flux 架构在实际开发中的应用逐渐减少,取而代之的是更简单、轻量级的解决方案。

Redux

简介

Redux 是 JavaScript 应用程序的可预测状态容器。

  • Redux 可以帮助你编写行为一致的应用程序,在不同的环境(客户端,服务器和本地)中运行,并且易于测试。
  • Redux 集中应用程序的状态和逻辑可以实现强大的功能,如撤销/重做、状态持久化等等。
  • Redux 的架构允许您记录更改,使用“时间旅行调试”,甚至向服务器发送完整的错误报告。
  • Redux 可与任何UI层配合使用,并提供大型插件生态系统以满足您的需求。

Redux 的核心概念

Store(存储) 是 Redux 中保存应用程序状态的地方。它是一个包含整个应用状态树的对象。通过 Redux 提供的 API,可以从 Store 中读取状态,也可以通过派发(dispatch)操作来修改状态。

Actions(动作) 是描述发生了什么的普通 JavaScript 对象。它们是唯一能够修改应用状态的方式。Actions 必须包含一个 type 属性,用于表示操作的类型,以及其他相关的数据。

Reducers(减速器) 是负责处理 Actions 的纯函数。Reducers 接收先前的状态和一个 Action,然后返回一个新的状态。Reducers 的设计原则是不要修改输入的状态对象,而是创建一个新的状态对象。

Dispatch(派发) 是一个用于发送 Action 的函数。当应用程序中的某些事情发生时,开发者可以调用 dispatch 方法,将一个 Action 发送给 Store。这将触发 Reducers 处理该 Action,并更新应用程序的状态。

Middleware(中间件) 是一个函数,用于拦截并处理派发的 Action。它可以用于异步操作、日志记录、路由等。Redux 的中间件机制允许开发者自定义处理 Action 的流程。

Selectors(选择器) 是用于从状态树中提取数据的函数。它可以帮助应用程序中的组件选择所需的数据,以便更容易地在应用程序中使用。

使用方法

// 导入 Redux
import { createStore } from 'redux';

// 定义 Reducer
const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
};

// 创建 Store
const store = createStore(counterReducer);

// 订阅状态变化
store.subscribe(() => {
  console.log('Current state:', store.getState());
});

// 派发 Actions
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'DECREMENT' });

Redux Toolkit 是 Redux 官方推荐的工具集,旨在简化和优化 Redux 的使用。它提供了一些实用的工具和约定,使 Redux 代码更具可读性、可维护性,并且减少了一些样板代码。

// counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    reset: (state) => {
      state.value = 0;
    },
  },
});

export const { increment, decrement, reset } = counterSlice.actions;
export default counterSlice.reducer;
// store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer,
  },
});

export default store;
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
// CounterComponent.js
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { increment, decrement, reset } from './counterSlice';

const CounterComponent = () => {
  const dispatch = useDispatch();
  const count = useSelector((state) => state.counter.value);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch(increment())}>Increment</button>
      <button onClick={() => dispatch(decrement())}>Decrement</button>
      <button onClick={() => dispatch(reset())}>Reset</button>
    </div>
  );
};

export default CounterComponent;

小结

推荐使用!

  • Redux 特点: 强大的状态管理库,单一不可变的状态树,通过 actions 触发状态的改变,适用于大型应用和复杂状态管理。

  • Redux 适用场景: 大型应用、需要时间旅行调试、强调一致性的状态管理。

  • Redux Toolkit 特点: Redux 的官方工具包,提供了一些工具函数来简化 Redux 的使用,适用于使用 Redux 的项目。

  • Redux Toolkit 适用场景: 使用 Redux 的项目、希望简化 Redux 开发流程。

MobX

简介

MobX 是一个身经百战的库,它通过运用透明的函数式响应编程(Transparent Functional Reactive Programming,TFRP)使状态管理变得简单和可扩展。

这么多状态管理库,你会选哪个?

MobX 的一些关键概念:

Actions(动作): 是用于改变状态的操作,可以是同步或异步的。在 MobX 中,通过 action 函数来定义动作。动作是唯一能够修改可观察对象状态的途径。

Observables(可观察对象): 是状态的容器,当这个对象的状态发生变化时,所有依赖于它的地方都会自动更新。在 MobX 中,通过 observable 函数将对象转化为可观察对象。

Computed Values(计算属性): 是从状态派生出来的值,它会自动地根据其依赖进行更新。在 MobX 中,通过 computed 函数定义计算属性。

Reactions(反应): 是在状态发生变化时自动执行的逻辑。在 MobX 中,通过 reaction 函数或 autorun 函数定义反应。反应是一种声明式地表达响应性的方式。

使用方法

import React from "react"
import ReactDOM from "react-dom"
import { makeAutoObservable } from "mobx"
import { observer } from "mobx-react"

// 对应用状态进行建模。
class Timer {
    secondsPassed = 0

    constructor() {
        makeAutoObservable(this)
    }

    increase() {
        this.secondsPassed += 1
    }

    reset() {
        this.secondsPassed = 0
    }
}

const myTimer = new Timer()

// 构建一个使用 observable 状态的“用户界面”。
const TimerView = observer(({ timer }) => (
    <button onClick={() => timer.reset()}>已过秒数:{timer.secondsPassed}</button>
))

ReactDOM.render(<TimerView timer={myTimer} />, document.body)

// 每秒更新一次‘已过秒数:X’中的文本。
setInterval(() => {
    myTimer.increase()
}, 1000)

小结

推荐使用!

  • MobX 特点: 简单而灵活,采用可变状态,通过观察者模式自动追踪状态的变化,适用于中小型应用和简单状态管理。
  • MobX 适用场景: 简单应用、需要更灵活的状态管理、对可变状态更为熟悉。

Valtio

简介

Valtio API 是最小的,灵活的,不固执己见的和触摸神奇。Valtio 的代理将你传递给它的对象变成一个自我感知的代理,在进行状态更新时允许细粒度的订阅和反应。

在 React 中,Valtio 在渲染优化方面大放异彩。它与 Suspense 和 React 18 兼容。Valtio 在普通 JavaScript 应用程序中也是一个可行的选择。

使用方法

// store.js
import { proxy, useSnapshot } from 'valtio';

export const store = proxy({ count: 0 });

// CounterComponent.js
import React from 'react';
import { store } from './store';

const CounterComponent = () => {
  const snapshot = useSnapshot(store);

  return (
    <div>
      <p>Count: {snapshot.count}</p>
      <button onClick={() => (store.count += 1)}>Increment</button>
    </div>
  );
};

export default CounterComponent;
// App.js
import React from 'react';
import CounterComponent from './CounterComponent';

const App = () => {
  return (
    <div>
      <h1>Valtio Example</h1>
      <CounterComponent />
    </div>
  );
};

export default App;

Recoil

简介

Recoil 是由 Facebook 开发的状态管理库,专门用于管理 React 应用中的全局状态。它提供了一种简单、直观的方式来处理组件之间共享的状态,并具有一些特性,使得在大型 React 应用中更加方便地管理状态。

Recoil 定义了一个有向图 (directed graph),正交同时又天然连结于你的 React 树上。状态的变化从该图的顶点(我们称之为 atom)开始,流经纯函数 (我们称之为 selector) 再传入组件。

Recoil 的一些关键概念:

Atoms(原子): 是 Recoil 中最基本的状态单元。它表示应用中的一个状态片段,并且可以被多个组件共享。每个 Atom 都有一个唯一的 key 用于标识。

Selectors(选择器): 允许你派生出一个新的状态,可以基于一个或多个 Atom。Selectors 允许你执行对状态的计算,这对于从全局状态中提取和组合数据非常有用。

RecoilRoot: 与 React 的 Context.Provider 类似,Recoil 提供了 RecoilRoot 组件,用于包装整个应用,以便在应用的任何地方都能访问 Recoil 状态。

useRecoilState 和 useRecoilValue: 这两个 Hook 分别用于获取 Atom 的状态和 Selector 的值。useRecoilState 返回一个数组,其中包含状态和更新状态的函数,而 useRecoilValue 只返回状态的值。

Recoil 支持按需加载状态。这允许应用程序在需要时动态地加载和卸载状态,以提高性能并减少初始加载时间。

使用方法

// atoms.js
import { atom, selector } from 'recoil';

export const counterState = atom({
  key: 'counterState',
  default: 0,
});

export const doubledCounter = selector({
  key: 'doubledCounter',
  get: ({ get }) => {
    const counter = get(counterState);
    return counter * 2;
  },
});
// CounterComponent.js
import React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { counterState, doubledCounter } from './atoms';

const CounterComponent = () => {
  const [counter, setCounter] = useRecoilState(counterState);
  const doubleCounter = useRecoilValue(doubledCounter);

  return (
    <div>
      <p>Counter: {counter}</p>
      <p>Doubled Counter: {doubleCounter}</p>
      <button onClick={() => setCounter(counter + 1)}>Increment</button>
    </div>
  );
};

export default CounterComponent;
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { RecoilRoot } from 'recoil';
import CounterComponent from './CounterComponent';

ReactDOM.render(
  <RecoilRoot>
    <CounterComponent />
  </RecoilRoot>,
  document.getElementById('root')
);

小结

推荐使用!

  • Recoil 特点: Facebook 出品,专注于简化状态管理,提供原子状态和选择器的概念,适用于中型应用和复杂状态共享。
  • 适用场景: 中型应用、复杂状态共享、希望简化状态管理的场景。

Jotai

简介

Jotai 采用原子方法进行全局 React 状态管理。

通过组合原子和渲染来构建状态会基于原子依赖性自动优化。这解决了 React 上下文的额外重新呈现问题,消除了对记忆的需要,并在维护声明式编程模型的同时提供了与信号类似的开发人员体验。

Jotai 特征:

  • 最小核心 API(2kb)
  • 许多实用程序和扩展
  • 面向 TypeScript
  • 支持 Next.js、Gatsby、Remix 和 React Native
  • 使用 SWC 和 Babel 插件进行 React 快速刷新

Jotai 核心:

Jotai 有一个非常小的 API,只公开了主要 jotai 包的一些导出。它们分为以下四类:

  • atom
  • useAtom
  • Store
  • Provider

使用方法

// atoms.js 
import { atom } from 'jotai'; 

export const countAtom = atom(0);
// CounterComponent.js
import React from 'react';
import { useAtom } from 'jotai';
import { countAtom } from './atoms';

const CounterComponent = () => {
  const [count, setCount] = useAtom(countAtom);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
       Increment
      </button>
    </div>
  );
};

export default CounterComponent;
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'jotai';
import CounterComponent from './CounterComponent';

ReactDOM.render(
  <React.StrictMode>
    <Provider>
      <CounterComponent />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

小结

推荐使用!

  • Jotai 特点: 简化的状态管理库,基于 React Hooks,原子状态的概念,适用于中小型应用和简单状态管理。
  • Jotai 适用场景: 中小型应用、简单状态管理、倾向于使用 React Hooks。

Zustand

简介

Zustand 是一个小型、快速、可扩展的 Bearbones 状态管理解决方案。Zustand 有一个基于钩子的舒适的 API。它不是样板或固执己见,但有足够的约定是明确的和通量一样。

vs Redux

  • Zustand 和 Redux 非常相似,都是基于不可变的状态模型。
  • Redux 要求应用被包装在上下文提供程序中; Zustand不需要。
// Zustand
import { create } from 'zustand'

type State = {
  count: number
}

type Actions = {
  increment: (qty: number) => void
  decrement: (qty: number) => void
}

type Action = {
  type: keyof Actions
  qty: number
}

const countReducer = (state: State, action: Action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + action.qty }
    case 'decrement':
      return { count: state.count - action.qty }
    default:
      return state
  }
}

const useCountStore = create<State & Actions>((set) => ({
  count: 0,
  dispatch: (action: Action) => set((state) => countReducer(state, action)),
}))
import { createSlice, configureStore } from '@reduxjs/toolkit'

const countSlice = createSlice({
  name: 'count',
  initialState: { value: 0 },
  reducers: {
    incremented: (state, qty: number) => {
      state.value += qty
    },
    decremented: (state, qty: number) => {
      state.value -= qty
    },
  },
})

const countStore = configureStore({ 
    reducer: countSlice.reducer 
})

vs Valtio

  • Zustand 和 Valtio 以完全不同的方式处理状态管理
  • Zustand 基于不可变状态模型,而 Valtio 基于可变状态模型。
// Zustand
import { create } from 'zustand'

type State = {
  obj: { count: number }
}

const store = create<State>(() => ({ obj: { count: 0 } }))

store.setState((prev) => ({ obj: { count: prev.obj.count + 1 } }))
// Valtio
import { proxy } from 'valtio'

const state = proxy({ obj: { count: 0 } })

state.obj.count += 1
  • Zustand 使用选择器手动应用渲染优化。
  • Valtio 通过属性访问进行渲染优化。
// Zustand
import { create } from 'zustand'

type State = {
  count: number
}

const useCountStore = create<State>(() => ({
  count: 0,
}))

const Component = () => {
  const count = useCountStore((state) => state.count)
  // ...
}
// Valtio
import { proxy, useSnapshot } from 'valtio'

const state = proxy({
  count: 0,
})

const Component = () => {
  const { count } = useSnapshot(state)
  // ...
}

vs Jotai

  • Zustand 是一个单一的存储,而 Jotai 由可以组合在一起的原始原子组成。
  • Zustand 存储是一个外部存储,这使得它更适合需要在 React 之外访问。
// Zustand
import { create } from 'zustand'

type State = {
  count: number
}

type Actions = {
  updateCount: (
    countCallback: (count: State['count']) => State['count'],
  ) => void
}

const useCountStore = create<State & Actions>((set) => ({
  count: 0,
  updateCount: (countCallback) =>
    set((state) => ({ count: countCallback(state.count) })),
}))
// Jotai
import { atom } from 'jotai'

const countAtom = atom<number>(0)
  • Zustand 使用选择器手动应用渲染优化。
  • Jotai 通过原子依赖实现渲染优化。
// Zustand
import { create } from 'zustand'

type State = {
  count: number
}

type Actions = {
  updateCount: (
    countCallback: (count: State['count']) => State['count'],
  ) => void
}

const useCountStore = create<State & Actions>((set) => ({
  count: 0,
  updateCount: (countCallback) =>
    set((state) => ({ count: countCallback(state.count) })),
}))

const Component = () => {
  const count = useCountStore((state) => state.count)
  const updateCount = useCountStore((state) => state.updateCount)
  // ...
}
// Jotai
import { atom, useAtom } from 'jotai'

const countAtom = atom<number>(0)

const Component = () => {
  const [count, updateCount] = useAtom(countAtom)
  // ...
}

vs Recoil

Recoil 依赖于原子字符串键,而不是原子对象引用标识。
Recoil 需要将应用程序包装在上下文提供程序中。

// zustand
import { create } from 'zustand'

type State = {
  count: number
}

type Actions = {
  setCount: (countCallback: (count: State['count']) => State['count']) => void
}

const useCountStore = create<State & Actions>((set) => ({
  count: 0,
  setCount: (countCallback) =>
    set((state) => ({ count: countCallback(state.count) })),
}))
import { atom } from 'recoil'

const count = atom({
  key: 'count',
  default: 0,
})
  • Zustand 使用选择器手动应用渲染优化。
  • Recoil 通过原子依赖性进行渲染优化。
// zustand
import { create } from 'zustand'

type State = {
  count: number
}

type Actions = {
  setCount: (countCallback: (count: State['count']) => State['count']) => void
}

const useCountStore = create<State & Actions>((set) => ({
  count: 0,
  setCount: (countCallback) =>
    set((state) => ({ count: countCallback(state.count) })),
}))

const Component = () => {
  const count = useCountStore((state) => state.count)
  const setCount = useCountStore((state) => state.setCount)
  // ...
}
// recoil
import { atom, useRecoilState } from 'recoil'

const countAtom = atom({
  key: 'count',
  default: 0,
})

const Component = () => {
  const [count, setCount] = useRecoilState(countAtom)
  // ...
}

小结

推荐使用!

  • Zustand 特点: 极简主义的状态管理库,通过 Hook 获取和更新状态,适用于中小型应用和简单状态管理。
  • Zustand 适用场景: 中小型应用、简单状态管理、希望使用极简的 API。

NPM 下载量

这么多状态管理库,你会选哪个?

github stars

这么多状态管理库,你会选哪个?

相关链接:npm-compare.com/@reduxjs/to…

Vuex

简介

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

Vuex 背后的基本思想:通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。

这么多状态管理库,你会选哪个?

Vuex 的一些主要概念:

State(状态): Vuex 的状态存储是响应式的,即当状态发生变化时,相关组件会自动更新。State 是存储应用级别状态的地方,可以通过 this.$store.state 访问。

Getter(获取器): 允许组件从 Store 中获取数据,类似于 Vue 组件中的计算属性。Getter 的返回值会根据它的依赖被缓存,只有在它的依赖值发生变化时才会重新计算。

Mutation(突变): Mutation 是唯一允许修改 Vuex Store 中状态的地方。每个 Mutation 都有一个字符串的事件类型(type)和一个回调函数,该回调函数接受当前的状态作为第一个参数。

Action(动作): Action 类似于 Mutation,但它提交的是 Mutation,而不是直接变更状态。Action 可以包含任意异步操作。在组件中通过 dispatch 方法触发 Action。

Module(模块): Vuex 允许将 Store 分割成模块(module),每个模块拥有自己的 state、getter、mutation、action。这有助于组织和管理大型应用的状态。

版本

  • 与 Vue 3 匹配的是 Vuex 4 版本。
  • 与 Vue 2 匹配的是 Vuex 3 版本。

对比全局对象

Vuex单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。(可以高效的更新)。
  2. 不能直接改变 store 中的状态。(可以方便地跟踪每一个状态的变化。)

使用方法

// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 0,
  },
  mutations: {
    increment(state) {
      state.count++;
    },
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment');
      }, 1000);
    },
  },
  getters: {
    doubleCount(state) {
      return state.count * 2;
    },
  },
});
// main.js
import Vue from 'vue';
import App from './App.vue';
import store from './store';

new Vue({
  render: (h) => h(App),
  store,
}).$mount('#app');
<!-- MyComponent.vue -->
<template>
  <div>
    <p>Count: {{ $store.state.count }}</p>
    <p>Double Count: {{ $store.getters.doubleCount }}</p>
    <button @click="$store.commit('increment')">Increment</button>
    <button @click="$store.dispatch('incrementAsync')">Increment Async</button>
  </div>
</template>

使用场景

当你需要构建一个中大型单页应用,考虑如更好地在组件外部管理状态的时候。

Pinia

介绍

Pinia 是 Vue 的专属状态管理库,它允许你跨组件或页面共享状态。如果你熟悉组合式 API 的话,你可能会认为可以通过一行简单的 export const state = reactive({}) 来共享一个全局状态。对于单页应用来说确实可以,但如果应用在服务器端渲染,这可能会使你的应用暴露出一些安全漏洞。 而如果使用 Pinia,即使在小型单页应用中,你也可以获得如下功能:

  • Devtools 支持

    • 追踪 actions、mutations 的时间线
    • 在组件中展示它们所用到的 Store
    • 让调试更容易的 Time travel
  • 热更新

    • 不必重载页面即可修改 Store
    • 开发时可保持当前的 State
  • 插件:可通过插件扩展 Pinia 功能

  • 为 JS 开发者提供适当的 TypeScript 支持以及自动补全功能。

  • 支持服务端渲染

vs Vuex

Pinia 起源于一次探索 Vuex 下一个迭代的实验,因此结合了 Vuex 5 核心团队讨论中的许多想法。最后,意识到 Pinia 已经实现了在 Vuex 5 中想要的大部分功能,所以决定将其作为新的推荐方案来代替 Vuex。

与 Vuex 相比,Pinia 不仅提供了一个更简单的 API,也提供了符合组合式 API 风格的 API,最重要的是,搭配 TypeScript 一起使用时有非常可靠的类型推断支持。

Pinia API 与 Vuex(<=4) 也有很多不同,即:

  • mutation 已被弃用。它们经常被认为是极其冗余的。它们初衷是带来 devtools 的集成方案,但这已不再是一个问题了。

  • 无需要创建自定义的复杂包装器来支持 TypeScript,一切都可标注类型,API 的设计方式是尽可能地利用 TS 类型推理。

  • 无过多的魔法字符串注入,只需要导入函数并调用它们,然后享受自动补全的乐趣就好。

  • 无需要动态添加 Store,它们默认都是动态的,甚至你可能都不会注意到这点。注意,你仍然可以在任何时候手动使用一个 Store 来注册它,但因为它是自动的,所以你不需要担心它。

  • 不再有嵌套结构的模块。你仍然可以通过导入和使用另一个 Store 来隐含地嵌套 stores 空间。虽然 Pinia 从设计上提供的是一个扁平的结构,但仍然能够在 Store 之间进行交叉组合。你甚至可以让 Stores 有循环依赖关系

  • 不再有可命名的模块。考虑到 Store 的扁平架构,Store 的命名取决于它们的定义方式,你甚至可以说所有 Store 都应该命名。

使用方法

// store/index.js
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    },
  },
  getters: {
    doubleCount() {
      return this.count * 2;
    },
  },
});
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import { createPinia } from 'pinia';

const app = createApp(App);
const pinia = createPinia();

app.use(pinia);
app.mount('#app');
<!-- MyComponent.vue -->
<template>
  <div>
    <p>Count: {{ $pinia.state.count }}</p>
    <p>Double Count: {{ $pinia.getters.doubleCount }}</p>
    <button @click="$pinia.actions.increment">Increment</button>
    <button @click="$pinia.actions.decrement">Decrement</button>
  </div>
</template>

<script>
import { useCounterStore } from '../store';

export default {
  setup() {
    const $pinia = useCounterStore();

    return {
      $pinia,
    };
  },
};
</script>

小结

推荐使用!

  • 如果你的项目是基于 Vue 3 的,你想要使用 Composition API,且对于新技术持开放态度,那么可以考虑使用 PiniaPinia 在性能和灵活性上都有很好的表现,适合现代化的 Vue 3 项目。
  • 如果你的项目是基于 Vue 2 的,或者你已经在使用 Vuex 并且没有特别的需求需要更换,那么继续使用 Vuex 也是一个很好的选择。Vuex 的成熟度和稳定性得到了广泛认可,适用于各种规模的 Vue.js 应用。

NPM 下载量

这么多状态管理库,你会选哪个?

相关链接:npm-compare.com/vuex,pinia

最后

其实还有很多的状态库,比如 Dva(不推荐)、Rematch、Hox 等等,在这里先也就不做详细介绍了。

我个人用的前端框架主要是 React 和 Vue。而针对这两个框架,我个人在项目基本上是:React 用 Redux;Vue2 用 Vuex;Vue3 用 Pinia。

你们呢?都在用哪些状态管理库?

原文链接:https://juejin.cn/post/7322275119593799743 作者:Bigger

(0)
上一篇 2024年1月11日 上午10:16
下一篇 2024年1月11日 上午10:27

相关推荐

发表回复

登录后才能评论