React常见状态管理库对比(Redux、Recoil、Zustand、Valtio、Jotai)

432 阅读4分钟

常见状态管理库

React的状态管理库很多,使用最多的应该是redux了,但是它使用起来很麻烦,于是有更多的备选方案,今天就来聊聊。

Redux

这是一个大家最为熟悉的 React 状态管理库,这里不多做赘述,基本使用如下:

import { createStore } from "redux";
import { useSelector, useDispatch } from "react-redux";

type State = {
  count: number;
};

type Action = {
  type: "increment" | "decrement";
  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 countStore = createStore(countReducer);

const Component = () => {
  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();
  // ...
};

在根组件添加 Provider,在组件中绑定数据:

import { useSelector } from "react-redux";
import type { TypedUseSelectorHook } from "react-redux";
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 });

const useAppSelector: TypedUseSelectorHook<typeof countStore.getState> =
  useSelector;

const useAppDispatch: () => typeof countStore.dispatch = useDispatch;

const Component = () => {
  const count = useAppSelector((state) => state.count.value);
  const dispatch = useAppDispatch();
  // ...
};
const App = () => {
  return (
    <Provider store={countStore}>
      <Counter />
    </Provider>
  );
};

可以看到 redux 的使用非常的复杂,它可能适用于一些大型项目做复杂的状态管理,但更多情况下我们不需要这么麻烦。

Recoil

使用 Recoil 会为你创建一个数据流向图,从  atom(共享状态)到  selector(纯函数),再流向 React 组件。Atom 是组件可以订阅的 state 单位。selector 可以同步或异步改变此 state。

基本使用如下:

import { atom, useRecoilState } from "recoil";

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

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

使用选择器状态

import { atom, selector, useRecoilValue } from "recoil";

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

const countState = selector({
  key: "countState",
  get: ({ get }) => {
    return get(countAtom);
  },
});

const Component = () => {
  const count = useRecoilValue(countAtom);
  // ...
};

recoil 必须在 RecoilRoot 组件下使用,这一点和 Redux 的 Provider 很像。

原子化的每个状态单元都是独立的,可以独立更新和订阅。这种状态管理方式提供了更细粒度的控制和更灵活的状态组合。

Zustand

创建 store,传入的回到函数接收两个参数setget,用于改变和获取自身的状态,zustand 不关心你的 action 函数是否异步,只用在合适的时候调用set

(虽然 setState 和 getState 两个 API 也可以达到相同效果,但是回调函数更符合单一责任原则)

import { create } from "zustand";

type CountState = {
  count: number;
};
type CountAction = {
  increase: () => void;
};

type CountStore = CountState & CountAction;

export const useCountStore = create<CountStore>((set) => ({
  count: 0,
  increase: () => set((state) => ({ count: state.count + 1 })),
}));

然后在组件内使用选择器绑定状态

const count = useCountStore((state) => state.count);
const increase = useCountStore((state) => state.increase);
// ...
<button onClick={increase}>count is {count}</button>;

你还可以在 store 外面修改它的状态

const decrease = () =>
  useCountStore.setState((state) => ({
    count: state.count + 1,
  }));

这样依然可以改变状态,这样做有两个好处:

  • 它不需要钩子来调用操作
  • 它有利于代码分割

当你需要在更新后立即获得最新的状态时,zustand 提供了getState方法

useCountStore.getState().count;

当你需要从 store 订阅一个计算状态,可能会造成不必要的渲染,官方给了一个例子:

import { create } from "zustand";

const useMeals = create(() => ({
  papaBear: "large porridge-pot",
  mamaBear: "middle-size porridge pot",
  littleBear: "A little, small, wee pot",
}));

export const BearNames = () => {
  const names = useMeals((state) => Object.keys(state));

  return <div>{names.join(", ")}</div>;
};

当执行下面语句时,state 发生了更改造成页面重新渲染

useMeals.setState({
  papaBear: "a large pizza",
});

实际上names的值并没有变,这造成了多余的重新渲染,官方提供了useShallow钩子解决这个问题,用法如下:

import { create } from "zustand";
import { useShallow } from "zustand/react/shallow";

const useMeals = create(() => ({
  papaBear: "large porridge-pot",
  mamaBear: "middle-size porridge pot",
  littleBear: "A little, small, wee pot",
}));

export const BearNames = () => {
  const names = useMeals(useShallow((state) => Object.keys(state)));

  return <div>{names.join(", ")}</div>;
};

如此一来就避免了不必要的重新渲染。

zustand 还提供了中间件,可以帮助实现持久化状态以防止数据丢失

总体而言,Zustand 是一个简单、灵活和高效的状态管理库,它提供了一种直观且易于理解的方式来管理状态,同时保持了良好的性能和可扩展性。

Valtio

创建 store,它可以直接使用自身的属性和方法。

import { proxy } from "valtio";

type CountStore = {
  count: number;
  increase: () => void;
};
const countStore = proxy<CountStore>({
  count: 0,
  increase: () => {
    countStore.count++;
  },
});

在组件中绑定状态

import { useSnapshot } from "valtio";
// ...
const { count, increase } = useSnapshot(countStore);

同样的它也可以在 store 外部修改

const increase = () => countStore.count++;

valtio 提供的是proxy而不是 store,这意味着它不提供订阅方法,这时候订阅它你需要用到subscribe函数

import { subscribe } from "valtio";

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

// 订阅状态proxy,返回取消该订阅的函数
const unsubscribe = subscribe(state, () =>
  console.log("state has changed to", state)
);
// 取消订阅
unsubscribe();

当你需要将一些依赖于某种状态的功能函数抽离成模块时这会很有用。

总的来说,valtio 的 API 同样非常简单,像是极简版的 zustand。

Jotai

jotai状态管理的一个特点是原子化,和recoil很像,但它有更简单的 API

import { atom, useAtom } from "jotai";

const countAtom = atom<number>(0);

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

这种原子状态是可扩展的,如下,此时的setState接收的参数作为update的值传入

const expandAtom = atom<number, [state: number], void>(
  (get) => get(countAtom),
  (get, set, update) => {
    set(countAtom, update + get(countAtom));
  }
);
//...
const [state, setState] = useAtom(expandAtom);

你也可以使用 store统一状态管理,在子组件内使用 useStore快速获取 store,操作它提供的getset方法绑定状态。它提供了sub方法用于在组件外订阅它。

const store = createStore();
store.sub(countAtom, () => {
  console.log(`countAtom is ${store.get(countAtom)}`);

};
store.set(countAtom, store.get(countAtom) + 1)

jotai还支持使用Provider提供不同的store,它不是必须的,无Provider模式将提供默认store

const Root = () => (
  <Provider store={store}>
    <App />
  </Provider>
);