2024热门的几个React状态管理库

redux

Redux 是一个用于可预测和可维护的全局状态管理的 JS 库。

安装依赖

安装依赖@reduxjs/toolkitreact-redux到项目中。

javascript 复制代码
npm install @reduxjs/toolkit react-redux

react-redux

react-redux 是React官方的Redux绑定库,由 Redux 团队直接维护的。

react-redux 在内部实现了许多性能优化,以便你编写的组件仅在实际需要时重新渲染。

此外,通过在 React 组件树中连接多个组件,你可以确保每个连接的组件仅从该组件所需的 store state 中提取特定的数据。 这意味着你自己的组件将需要更少的重新渲染,因为大多数时候这些特定的数据都没有改变。

@reduxjs/toolkit

@reduxjs/toolkit(Redux工具包,简称 "RTK"),是官方推荐的 Redux 逻辑编写方法。@reduxjs/toolkit 包围绕核心 redux 包,并包含对于构建 Redux 应用至关重要的 API 方法和常见依赖。Redux Toolkit 构建了官方建议的最佳实践,简化了大多数 Redux 任务,防止常见错误,并使编写 Redux 应用变得更加容易。

使用

创建store.ts文件

typescript 复制代码
import { configureStore } from "@reduxjs/toolkit";

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

创建 store 后,我们可以通过在 src/main.tsx 中的应用程序外层放置一个 React Redux <Provider> 来使其对我们的 React 组件可用。导入我们刚刚创建的 Redux store,在 <App> 的外层放置一个 <Provider>,并将 store 作为 prop 传递:

tsx 复制代码
import { createRoot } from "react-dom/client";
import "./index.css";
import { RouterProvider } from "react-router-dom";
import router from "./route";
import { Provider } from "react-redux";
import store from "./store";

const root = createRoot(document.getElementById("root") as HTMLElement);

root.render(
  <Provider store={store}>
    <RouterProvider router={router} />
  </Provider>
);

创建 Redux State Slice

typescript 复制代码
import { createSlice, PayloadAction } from "@reduxjs/toolkit";

export const userSlice = createSlice({
  name: "user", // 字符串名称标识 
  initialState: {
    age: 0,
    name: "",
  },
  reducers: {
    changeAge: (state, action: PayloadAction<number>) => {
      state.age = action.payload;
    },
    changeName: (state, action: PayloadAction<string>) => {
      state.name = action.payload;
    },
  },
});

export const { changeAge, changeName } = userSlice.actions;

export default userSlice.reducer;

添加 Slice Reducers 到 Store

typescript 复制代码
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./user";

const store = configureStore({
  reducer: {
    user: userReducer,
  },
});

// 从 store 本身推断 `RootState` 和 `AppDispatch` 类型
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;

在组件中使用,用hooks的写法,我们可以使用 useSelector 从 store 中读取数据,并使用 useDispatch dispatch actions。

我们创建A,B两个组件,A组件负责展示,B组件负责修改。

tsx 复制代码
// A组件
import { useSelector } from "react-redux";

export default function AComponent() {
  const userName = useSelector((state) => state.user.name);
  const userAge = useSelector((state) => state.user.age);

  return (
    <div>
      <h3>A组件</h3>
      <div>用户姓名:{userName}</div>
      <div>用户年龄:{userAge}</div>
    </div>
  );
}

这时候发现有ts的类型问题

我们可以创建 useDispatchuseSelector hooks 类型化版本以供在应用程序中使用。

typescript 复制代码
import { configureStore } from "@reduxjs/toolkit";
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import userReducer from "./user";

const store = configureStore({
  reducer: {
    user: userReducer,
  },
});

// 从 store 本身推断 `RootState` 和 `AppDispatch` 类型
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// 创建 `useDispatch` 和 `useSelector` hooks 类型化版本
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export default store;

修改A组件

tsx 复制代码
// A组件
import { useAppSelector } from "@/store/index";
export default function AComponent() {
  const userName = useAppSelector((state) => state.user.name);
  const userAge = useAppSelector((state) => state.user.age);

  return (
    <div>
      <h3>A组件</h3>
      <div>用户姓名:{userName}</div>
      <div>用户年龄:{userAge}</div>
    </div>
  );
}
tsx 复制代码
// B组件
import { useAppDispatch } from "@/store/index";
import { changeAge, changeName } from "@/store/user";

export default function BComponent() {
  const dispatch = useAppDispatch();

  const changeUserName = (val: string) => {
    dispatch(changeName(val));
  };
  const changeUserAge = (val: string) => {
    dispatch(changeAge(Number(val)));
  };

  return (
    <div>
      <h3>B组件</h3>
      <div>
        <span>修改用户姓名:</span>
        <input
          type="text"
          onChange={(e) => changeUserName(e.currentTarget.value)}
        />
      </div>
      <div>
        <span>修改用户年龄:</span>
        <input
          type="number"
          onChange={(e) => changeUserAge(e.currentTarget.value)}
        />
      </div>
    </div>
  );
}

将两个组件一起引入同一页面演示一下

tsx 复制代码
import "./index.scss";
import AComponent from "./AComponent";
import BComponent from "./BComponent";

export default function Demo() {
  return (
    <div className="wrap">
      <div className="component-wrap">
        <AComponent></AComponent>
      </div>
      <div className="component-wrap">
        <BComponent></BComponent>
      </div>
    </div>
  );
}

zustand

基于 Flux 模型实现的小型、快速和可扩展的状态管理解决方案,拥有基于 hooks 的舒适的API,非常地灵活且有趣。

安装依赖

javascript 复制代码
npm install zustand

使用

Store 初始化

用 create 函数创建一个 store,创建的 store 是一个 hook,你可以放任何东西到里面:基础变量,对象、函数,状态必须不可改变地更新,set 函数合并状态以实现状态更新。

typescript 复制代码
import { create } from "zustand";

interface UserState {
  name: string;
  age: number;
  changeName: (val: string) => void;
  changeAge: (val: number) => void;
}

export const useUserStore = create<UserState>((set) => ({
  name: "",
  age: 0,
  changeName: (value: string) => set(() => ({ name: value })),
  changeAge: (value: number) => set({ age: value }),
}));

Store 绑定组件

tsx 复制代码
// A组件
import { useUserStore } from "@/store/user";
export default function AComponent() {
  const userName = useUserStore((state) => state.name);
  const userAge = useUserStore((state) => state.age);
  return (
    <div>
      <h3>A组件</h3>
      <div>用户姓名:{userName}</div>
      <div>用户年龄:{userAge}</div>
    </div>
  );
}
tsx 复制代码
// B组件
import { useUserStore } from "@/store/user";

export default function BComponent() {
  const changeUserName = useUserStore((state) => state.changeName);
  const changeUserAge = useUserStore((state) => state.changeAge);
  const ageInputChange = (val: string) => {
    changeUserAge(Number(val));
  };

  return (
    <div>
      <h3>B组件</h3>
      <div>
        <span>修改用户姓名:</span>
        <input
          type="text"
          onChange={(e) => changeUserName(e.currentTarget.value)}
        />
      </div>
      <div>
        <span>修改用户年龄:</span>
        <input
          type="number"
          onChange={(e) => ageInputChange(e.currentTarget.value)}
        />
      </div>
    </div>
  );
}

结果如下

Recoil

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

安装依赖

javascript 复制代码
npm install recoil

使用

RecoilRoot

要在组件中使用 Recoil,可以将 RecoilRoot 放置在父组件的某个位置。最好直接将其放在根组件。

tsx 复制代码
import { createRoot } from "react-dom/client";
import "./index.css";
import { RouterProvider } from "react-router-dom";
import router from "./route";
import { RecoilRoot } from "recoil";

const root = createRoot(document.getElementById("root") as HTMLElement);

root.render(
  <RecoilRoot>
    <RouterProvider router={router} />
  </RecoilRoot>
);

Atom

Atom 是状态的单位。它们可更新也可订阅:当 atom 被更新,每个被订阅的组件都将使用新值进行重渲染。它们也可以在运行时创建。可以使用 atom 替代组件内部的 state。如果多个组件使用相同的 atom,则这些组件共享 atom 的状态。

Atom 是使用 atom 函数创建的

javascript 复制代码
import { atom } from "recoil";
export const nameState = atom({
  key: "nameState", // Atom 需要一个唯一的 key 值,请确保它们在全局上的唯一性
  default: "",
});
export const ageState = atom({
  key: "ageState", // Atom 需要一个唯一的 key 值,请确保它们在全局上的唯一性
  default: 0,
});

Selector

selector 是一个纯函数,入参为 atom 或者其他 selector。当上游 atom 或 selector 更新时,将重新执行 selector 函数。组件可以像 atom 一样订阅 selector,当 selector 发生变化时,重新渲染相关组件。

tsx 复制代码
import { atom, selector } from "recoil";

export const nameState = atom({
  key: "nameState",
  default: "",
});

export const ageState = atom({
  key: "ageState",
  default: 0,
});

export const introductionState = selector({
  key: "introductionState",
  get: ({ get }) => {
    const name = get(nameState);
    const age = get(ageState);
    return `${name}今年${age}岁了`;
  },
});

get 属性是用于计算的函数。它可以使用名为 get 的入参来访问 atom 以及其他 selector 的值。每当它访问另一个 atom 或 selector 时,就会创建相应的依赖关系,以便在更新另一个 atom 或 selector 时,该 atom 或 selector 也会被重新计算。

useRecoilState、useRecoilValue

要从组件中读取和写入 atom,我们需使用一个名为 useRecoilState 的 hook。和 React 的 useState 用法一致,但是这里的 state 可以在组件间共享使用。

可以使用 useRecoilValue() 读取 selector。它使用 atom 或 selector 作为参数,并返回相应的值。

tsx 复制代码
// A组件
import { useRecoilState, useRecoilValue } from "recoil";
import { nameState, ageState, introductionState } from "@/store/user";

export default function AComponent() {
  const [name] = useRecoilState(nameState);
  const [age] = useRecoilState(ageState);
  const introduction = useRecoilValue(introductionState);
  return (
    <div>
      <h3>A组件</h3>
      <div>用户姓名:{name}</div>
      <div>用户年龄:{age}</div>
      <div>{introduction}</div>
    </div>
  );
}
tsx 复制代码
// B组件
import { useRecoilState, useRecoilValue } from "recoil";
import { nameState, ageState, introductionState } from "@/store/user";

export default function BComponent() {
  const [name, setName] = useRecoilState(nameState);
  const [age, setAge] = useRecoilState(ageState);

  const changeUserName = (val: string) => {
    setName(val);
  };
  const changeUserAge = (val: string) => {
    setAge(Number(val));
  };

  return (
    <div>
      <h3>B组件</h3>
      <div>
        <span>修改用户姓名:</span>
        <input
          type="text"
          onChange={(e) => changeUserName(e.currentTarget.value)}
        />
      </div>
      <div>
        <span>修改用户年龄:</span>
        <input
          type="number"
          onChange={(e) => changeUserAge(e.currentTarget.value)}
        />
      </div>
    </div>
  );
}

结果如下:

Jotai

Jotai 是一个原始而灵活的 React 状态管理库。 它采用自下而上的方法,采用受 Recoil 启发的原子模型。

安装依赖

javascript 复制代码
npm install jotai

使用

atom

atom 功能是创建一个原子配置。

typescript 复制代码
import { atom } from "jotai";

export const nameAtom = atom("");
export const ageAtom = atom(0);

还可以创建派生原子。 有三种模式:

  • 只读 atom
  • 只写 atom
  • 读写 atom
typescript 复制代码
import { atom } from "jotai";

export const nameAtom = atom("");
export const ageAtom = atom(0);
// 只读 atom
export const introductionAtom = atom(
  (get) => `${get(nameAtom)}今年${get(ageAtom)}岁了`
);
// 只写 atom
export const changeNameAtom = atom(
  null, // 为第一个参数传递 `null` 是一种惯例
  (get, set, param: string) => {
    set(nameAtom, param);
  }
);
// 读写 atom
export const readWriteAgeAtom = atom(
  (get) => get(ageAtom) + "岁",
  (get, set, param: number) => {
    set(ageAtom, param);
  }
);

useAtom

useAtom hook 用于读取状态中的原子值。useAtom hook 以元组形式返回原子值和更新函数, 就像 React 的 useState 一样。 它需要一个使用 atom() 创建的原子配置。

除了useAtom,还有常用的useAtomValueuseSetAtom

  • useAtomValuehook用于读取状态中的原子值,只能读取。

  • useSetAtomhook则用于获取写入函数。

tsx 复制代码
// A组件
import { useAtom, useAtomValue } from "jotai";
import { nameAtom, readWriteAgeAtom, introductionAtom } from "@/store/user";

export default function AComponent() {
  const [name] = useAtom(nameAtom);
  const [age] = useAtom(readWriteAgeAtom);
  const introduction = useAtomValue(introductionAtom);
  return (
    <div>
      <h3>A组件</h3>
      <div>用户姓名:{name}</div>
      <div>用户年龄:{age}</div>
      <div>{introduction}</div>
    </div>
  );
}
tsx 复制代码
// B组件
import { useAtom, useSetAtom } from "jotai";
import {
  nameAtom,
  ageAtom,
  readWriteAgeAtom,
  changeNameAtom,
} from "@/store/user";

export default function BComponent() {
  const [, setName] = useAtom(nameAtom);
  const [, setAge] = useAtom(ageAtom);
  const setUserName = useSetAtom(changeNameAtom);
  const setUserAge = useSetAtom(readWriteAgeAtom);

  return (
    <div>
      <h3>B组件</h3>
      <div>
        <span>修改用户姓名:</span>
        <input type="text" onChange={(e) => setName(e.currentTarget.value)} />
      </div>
      <div>
        <span>修改用户姓名2:</span>
        <input
          type="text"
          onChange={(e) => setUserName(e.currentTarget.value)}
        />
      </div>
      <div>
        <span>修改用户年龄:</span>
        <input
          type="number"
          onChange={(e) => setAge(Number(e.currentTarget.value))}
        />
      </div>
      <div>
        <span>修改用户年龄2:</span>
        <input
          type="number"
          onChange={(e) => setUserAge(Number(e.currentTarget.value))}
        />
      </div>
    </div>
  );
}

结果如下

用useSetAtom有时候可以起到性能优化的作用。

例如我们修改一下B组件做个演示

tsx 复制代码
// B组件
import { useAtom, useSetAtom } from "jotai";
import { nameAtom, changeNameAtom } from "@/store/user";

export default function BComponent() {
  const [, setName] = useAtom(nameAtom);
  
  console.log("B组件渲染了");

  return (
    <div>
      <h3>B组件</h3>
      <div>
        <span>修改用户姓名:</span>
        <input type="text" onChange={(e) => setName(e.currentTarget.value)} />
      </div>
    </div>
  );
}

可以发现B组件在修改状态时会触发重新渲染,但其实这里B组件只做修改功能,不需要重新渲染。

而改成useSetAtom后

tsx 复制代码
// B组件
import { useAtom, useSetAtom } from "jotai";
import { nameAtom, changeNameAtom } from "@/store/user";

export default function BComponent() {
  const setName = useSetAtom(changeNameAtom);

  console.log("B组件渲染了");

  return (
    <div>
      <h3>B组件</h3>
      <div>
        <span>修改用户姓名:</span>
        <input type="text" onChange={(e) => setName(e.currentTarget.value)} />
      </div>
    </div>
  );
}

可以发现换成useSetAtom后就不会触发B组件重新渲染了。

valtio

Valtio使React的代理状态变得简单。

安装依赖

tsx 复制代码
npm i valtio

基本Api

valtio的基本api有proxyuseSnapshot

  • proxy: 代理跟踪原始对象和所有嵌套对象的变化
  • useSnapshot: 创建捕捉变化的本地快照。

使用

typescript 复制代码
import { proxy } from "valtio";

export const user = proxy({
  name: "",
  age: 0,
});
tsx 复制代码
// A组件
import { useSnapshot } from "valtio";
import { user } from "@/store/user";

export default function AComponent() {
  const userSnap = useSnapshot(user);
  return (
    <div>
      <h3>A组件</h3>
      <div>用户姓名:{userSnap.name}</div>
      <div>用户年龄:{userSnap.age}</div>
    </div>
  );
}
tsx 复制代码
// B组件
import { user } from "@/store/user";

export default function BComponent() {
  return (
    <div>
      <h3>B组件</h3>
      <div>
        <span>修改用户姓名:</span>
        <input
          type="text"
          onChange={(e) => (user.name = e.currentTarget.value)}
        />
      </div>
      <div>
        <span>修改用户年龄:</span>
        <input
          type="number"
          onChange={(e) => (user.age = Number(e.currentTarget.value))}
        />
      </div>
    </div>
  );
}

结果如下

hox

与redux、zustand、recoil 这些状态管理库不同的是,它们最本质的能力还是对数据的操作。而Hox 想解决的问题,不是如何组织和操作数据,不是数据流的分层、异步、细粒度,Hox作者希望 Hox 只聚焦于一个痛点:在多个组件间共享状态。

安装依赖

javascript 复制代码
npm i hox

全局store

可以通过 createGlobalStore 来创建一个全局 store

typescript 复制代码
import { useState } from "react";
import { createGlobalStore } from "hox";

export const [useUseStore, getUserStore] = createGlobalStore(() => {
  const [name, setName] = useState("");
  const [age, setAge] = useState(0);

  function changeName(name: string) {
    setName(name);
  }

  function changeAge(age: number) {
    setAge(age);
  }

  return {
    name,
    age,
    changeName,
    changeAge,
  };
});

createGlobalStore 返回了一个数组,里面有两个元素,你可以通过 ES6 的数组解构语法把他们解构出来

第一个元素是用来订阅和消费 store 的 Hook 函数;

第二个元素是一次性地读取 store 当前最新的值,但不会触发持续的订阅,因为它不是 Hook

tsx 复制代码
// A组件
import { useUseStore, getUserStore } from "@/store/user";

export default function AComponent() {
  const { name, age } = useUseStore();

  function getStore() {
    const store = getUserStore();
    console.log(store);
  }

  return (
    <div>
      <h3>A组件</h3>
      <div>用户姓名:{name}</div>
      <div>用户年龄:{age}</div>
      <button onClick={getStore}>获取store数据</button>
    </div>
  );
}
tsx 复制代码
// B组件
import { useUseStore } from "@/store/user";

export default function BComponent() {
  const { changeName, changeAge } = useUseStore();

  return (
    <div>
      <h3>B组件</h3>
      <div>
        <span>修改用户姓名:</span>
        <input
          type="text"
          onChange={(e) => changeName(e.currentTarget.value)}
        />
      </div>
      <div>
        <span>修改用户年龄:</span>
        <input
          type="number"
          onChange={(e) => changeAge(Number(e.currentTarget.value))}
        />
      </div>
    </div>
  );
}

store

除了全局store外,还可以用createStore创建普通store

在 hox 中,任意的 custom Hook,经过 createStore 包装后,就变成了持久化,可以在组件间进行共享的状态。

tsx 复制代码
import { useState } from "react";
import { createStore } from "hox";

const [useCountStore, CountStoreProvider] = createStore(() => {
  const [count, setCount] = useState(0);

  function addCount() {
    setCount((v) => v + 1);
  }
  return {
    count,
    addCount,
  };
});

// A组件
function AComponent() {
  const { count } = useCountStore();
  return (
    <div className="component-wrap">
      <h3>A组件</h3>
      <div>
        <span>数值:</span>
        <div>{count}</div>
      </div>
    </div>
  );
}

// B组件
function BComponent() {
  const { addCount } = useCountStore();
  return (
    <div className="component-wrap">
      <h3>B组件</h3>
      <div>
        <button onClick={addCount}>增加</button>
      </div>
    </div>
  );
}

export default function CComponent() {
  return (
    <div>
      <CountStoreProvider>
        <AComponent></AComponent>
        <BComponent></BComponent>
      </CountStoreProvider>
    </div>
  );
}

createStore 会返回一个数组,里面有两个元素,你可以通过 ES6 的数组解构语法把他们解构出来

第一个元素是用来订阅和消费 store 的 Hook 函数(和createGlobalStore返回的第一个元素一样);

第二个元素是是状态的容器,它的底层是依赖了 React Context 所以你需要把它注入到组件树中。

上面代码效果如下:

状态管理库对比

可以从 npm trends 看各个状态管理库近一年的下载量趋势

可以发现,Redux作为老牌状态管理库,地位依然稳固。但是近几年出现的jotai、zustand、valtio、recoil等新的状态管理库,由于它们更简单的操作,更小的体积,更好的性能等优点,热度也在逐步上升,特别是zustand,是其中最流行的一个。而hox则用的人还是比较少。

redux zustand jotai recoil valtio hox
github Stars 60.6k 44.6k 17.8k 19.5k 8.7k 1.4k
created 2011 2019 2020 2020 2020 2019
相关推荐
雯0609~10 分钟前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ14 分钟前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z19 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
彭世瑜43 分钟前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund40444 分钟前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish44 分钟前
Token刷新机制
前端·javascript·vue.js·typescript·vue
小五Five1 小时前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序1 小时前
vue3 封装request请求
java·前端·typescript·vue
临枫5411 小时前
Nuxt3封装网络请求 useFetch & $fetch
前端·javascript·vue.js·typescript
前端每日三省1 小时前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript