第十一章 这三个全局状态管理库之间的共性与差异 【上】

在本书,我们讨论了三个全局状态管理库:Zutand,Jotai 和 Valtio。让我们比较这三个库之间的共性与差异。这三个库,都有其独自的特性。

Zustand 在用法和 存储模型上,与 Redux类似。但是,Redux是 基于 reducers 函数来更新的。

Jotai 在 API 上 和 Recoil 类似,但Jotai的目的是提供一个 非基选择函数 渲染优化 的最小化 API。

Valtio 和 Mobx 在 更新方式上是一样的,都是可变式更新。但这两者的相似性就仅限于此了,它们实现 自动渲染优化的方式是不一样的。

三个库都提供了适合微状态管理的原始功能,但它们在编码风格和渲染优化方法上有所不同。

在本章,我们会通过比较这三个库的对标库的方式,讨论这三个库。之后,我们会讨论这三个库的共性与差异。我们会涵盖这几个主题:

  • Zustand 与 Redux 的异同
  • 理解何时使用 Jotai 和 Recoil
  • 使用 Valito 和 MobX
  • 比较 Zustand,Jotai 和 Valtio

Zustand 与 Redux 的异同

在一些情况下,Zustand 和 Redux 用起来是很相似的。这两者都是基于单项数据流的。在单向数据流中,我们要分发 代表 更新一个状态的指令 的 action函数,而当 状态被 action更新后,新的 状态会被 传导到需要用到它当 地方。把分发 action函数和 传导新状态分开后,数据的流转过程被简化了,整个系统也因此更具有可预测性。

另一方面,它们在更新方式上不同。Redux 的更新是 基于 reducer函数的。一个reducer函数,是以状态和action对象为参数的 纯函数,并返回 更新后的状态。尽管使用 reducer函数来更新要遵循更严格的规范,但这也 提升了 代码 的 可预测性。而Zustand则使用了更灵活的方式来更新状态,并不需要使用 reducer函数。

在这个 小节,我们会把一个 基于 Redux的例子 改造成以Zustand为状态管理库的例子,并在这个过程中比较这两者的不同。

Redux 和 Zustand 的例子

让我们看看 Redux 的官方 教程。这个教程也叫 基于 React-toolkit 的 现代 Redux。

我们可以用 来自 React Toolkit 的 configureStore 来创建一个 Redux 仓库:

js 复制代码
// src/app/store.js
import { configureStore } from "@reudxjs/toolkit";
import counterReducer from "../features/counter/counterSlice";

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

这个 configureStore 函数 以一个reducer 函数为参数,并返回一个 store变量。在这段代码中,它使用了一个 reducer 函数 - counterReducer

counterReducer写在了一个独立的文件里。首先,我们要引入createSlice,并定义initialState

js 复制代码
// features/counter/counterSlice.js
import { createSlice } from "@reudxjs/toolkit";

const initialState = {
    value: 0
}

之后,我们用 createSliceinitialState 来 定义 counterSlice:

js 复制代码
export const counterSlice = createSlice({
    name: "Counter",
    initialState,
    reducers: {
        increment: (state) => {
            state.value += 1
        },
        decrement: (state) => {
            state.value -= 1
        },
        incrementByAmount: (
            state,
            action: PayloadAction<number>
        ) => {
            state.value = action.payload;
        }
    }
})

变量 counterSlice 是使用 createSlice 函数创建的,它同时包含一个 reducer 和 actions。为了方便导入它们,我们提取了 reducer 和 action 属性并将它们分别导出:

js 复制代码
export const {
    increment,
    decrement,
    incrementByAmount
} = counterSlice.aciton;
export defalut counterSlice.reducer;

之后,我们要定义 使用 仓库的 Counter组件。首先,我们要从 react-redux 引入两个 常用的钩子,并从 counterSlice 引入 两个 actions 函数。

js 复制代码
// features/counter/Counter.jsx
import { useSelector, useDispatch } from "react-redux";
import { increment, decrement } from "./counterSlice";

然后,就是ui逻辑了:

js 复制代码
export function Counter() {
    const count = useSelector((
        state: { counter: { value: number } }
    ) => state.counter.value);
    const dispatch = useDispatch();
    
    return (
        <div>
            <button onClick={() => dispatch(increment())}>
                Increment
            </button>
            <span>{count}</span>
            <button onClick={() => dispatch(decrement())}>
                decrement
            </button>
        </div>
    )
}

这个组件,使用了来自 React Redux 的 useSelectoruseDispatch 函数。我们使用了 选择函数来 获得 仓库 内的值。注意,这个组件并没有直接从 仓库获取值。

最后,我们实现App 组件:

js 复制代码
// App.jsx
import { Provider } from "react-redux";
import { store } from "./app/store";
import { Counter } from "./features/counter/Counter";

const App = () => {
    <Provider store={store}>
        <div>
            <Counter/>
            <Counter/>
        </div>
    </Provider>
}

export default App;

我们把 store 传给 Provider组件。有了Provider组件后,useSelector钩子就可以在这个Context 内 获取 仓库的值了。

如下图所示,代码按照预期运行了。这两个Counter 组件共享同一个 count变量:

然后,我们看看这个功能,如何用 Zustand实现。

首先,我们要从 Zustand 引入 create 函数:

js 复制代码
// store.js
import create from "zustand";

之后,我们用TS定义 State 类型:

js 复制代码
type State = {
    counter: {
        value: number
    },
    counterActions: {
        increment: () => void;
        decrement: () => void:
        incrementByAmount: (amount: number) => void;
    }
}

接下来,我们要定义一个store。在zustand,我们用 useStore 来代表一个 仓库:

js 复制代码
export const useStore = create<State>((set) => ({
    counter: { value: 0 },
    counterActions: {
        increment: () => {
            set((state) => ({
                counter: { value: state.counter.value + 1 }
            }))
        },
        decrement: () => {
            set((state) => ({
                counter: { value: state.counter.value - 1 }
            }))
        },
        incrementByAmount: (amount: number) => {
            set((state) => ({
                counter: { value: state.counter.value + amount }
            }))
        }
    }
}))

这段代码定义了 counter 的状态 以及 counter 的 操作函数。

接下来,就是 实现 使用了 这个 仓库的 Counter组件了:

js 复制代码
// Counter.jsx
import { useStore } from "./store";

export function Counter() {
    const count = useStore((state) => state.counter.value);
    const { increment, decrement } = useStore(
        (state) => state.counterActions
    );
    
    return (
        <div>
            <button onClick={increment}>
                Increment
            </button>
            <span>{count}</span>
            <button onClick={decrement}>
                decrement
            </button>
        </div>
    )
}

我们使用 useStore 来获取 counter 的值, 和 counter的 操作函数。需要注意的是,useStore这个钩子是直接从 store 文件引入的

最后,我们可以定义 App组件:

js 复制代码
// App.jsx
import { Counter } from "./Counter";

const App = () => {
    <div>
        <Counter />
        <Counter />
    </div>
}

因为我们没有使用Context,所以这里不需要使用 Provider组件。

接下来,我们要比较这两者的不同。

比较 Zustand 与 Redux

尽管刚刚的两个例子有很多共性,Zustand 和 Redux 还是有 很多 显著的 不同的:

  • 这两个例子,最大的不同在于,文件结构。现代Redux 所推荐的是 features 目录结构,而 createSlice 方法也是为这个结构而设计的。这个模式对于 大型应用是非常有用的。至于 Zustand,是不在乎文件结构的,开发者可以 自行决定 文件目录结构。

  • 第二不同是,是否采用了 Immer.js。Immer.js 允许开发者 以 state.value += 1的风格来更新状态;现代Redux 是 默认 使用 Immer.js的。Zustand 本身 没有 默认使用 Immer.js, 我们的例子里, Zustand仓库也没有 使用 Immer.js。不过,也是可以在 Zustand 里 使用 Immer.js 的。

  • 第三个不同,在于状态的传递。Redux 采用 Context 注入状态,而 Zustand 主要是 通过模块引入。Context 允许 状态 在运行时 被注入。Zustand 允许 开发者 通过 Context 来 注入 状态。

  • 最重要的是,Redux toolkit 是 基于 支持单向数据流的 Redux。所以,在Redux 来更新值,需要通过 reducer 来 分发状态。这一 限制和规范,有时对于 代码的 可维护性 和 可拓展性是 很有帮助的。至于 Zustand,是 不关心 单向数据流的。

总结来说,现代 Redux更 关注 仓库的 文件结构,而 Zustand 则 没那么关注。本质上来说,Zustand 是 一个最小化的 状态管理库,而 Redux 背后 有更完整的 生态。虽然两者的用法相似,但其背后的 哲学是 非常不一样的。

在下一个小节, 我们要 比较 Recoil 和 Jotai。

相关推荐
小桥风满袖几秒前
Three.js-硬要自学系列33之专项学习基础材质
前端·css·three.js
聪明的水跃鱼5 分钟前
Nextjs15 构建API端点
前端·next.js
小明爱吃瓜22 分钟前
AI IDE(Copilot/Cursor/Trae)图生代码能力测评
前端·ai编程·trae
水冗水孚24 分钟前
🚀四种方案解决浏览器地址栏预览txt文本乱码问题🚀Content-Type: text/plain;没有charset=utf-8
javascript·nginx·node.js
不爱说话郭德纲27 分钟前
🔥Vue组件的data是一个对象还是函数?为什么?
前端·vue.js·面试
绅士玖29 分钟前
JavaScript 中的 arguments、柯里化和展开运算符详解
前端·javascript·ecmascript 6
每天都想着怎么摸鱼的前端菜鸟31 分钟前
【uniapp】uniapp热更新WGT资源,简单的多环境WGT打包脚本
javascript·uni-app
GIS之路32 分钟前
OpenLayers 图层控制
前端
断竿散人32 分钟前
专题一、HTML5基础教程-http-equiv属性深度解析:网页的隐形控制中心
前端·html
星河丶32 分钟前
介绍下navigator.sendBeacon方法
前端