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
相关推荐
小小小小宇8 分钟前
业务项目中使用自定义eslint插件
前端
babicu12312 分钟前
CSS Day07
java·前端·css
小小小小宇16 分钟前
业务项目使用自定义babel插件
前端
前端码虫37 分钟前
JS分支和循环
开发语言·前端·javascript
GISer_Jing39 分钟前
MonitorSDK_性能监控(从Web Vital性能指标、PerformanceObserver API和具体代码实现)
开发语言·前端·javascript
余厌厌厌40 分钟前
墨香阁小说阅读前端项目
前端
fanged41 分钟前
Angularjs-Hello
前端·javascript·angular.js
lichuangcsdn42 分钟前
springboot集成websocket给前端推送消息
前端·websocket·网络协议
程序员阿龙42 分钟前
基于Web的濒危野生动物保护信息管理系统设计(源码+定制+开发)濒危野生动物监测与保护平台开发 面向公众参与的野生动物保护与预警信息系统
前端·数据可视化·野生动物保护·濒危物种·态环境监测·web系统开发
岸边的风1 小时前
JavaScript篇:JS事件冒泡:别让点击事件‘传染’!
开发语言·前端·javascript