Zustand 和 React 上下文状态管理

devtools/2024/9/25 10:25:16/

Zustand 是客户端全局状态管理的一个很棒的库。它简单、快速,并且包大小小。然而,有一件事我不一定喜欢它:这些 Store 是全局性的。

但这不是全局状态管理的重点吗?要使该状态在您的应用程序中随处可用。不过当我回顾过去几年中使用 zustand 的情况时,我意识到,更多时候我需要在全局范围内将某些状态提供给一个组件子树,而不是整个应用程序。

使用 zustand,完全可以(甚至可以鼓励)按功能创建多个小型存储。那么,如果我只需要在仪表板路由中使用仪表板过滤器存储,为什么还要在全局范围内使用它呢?当然,我可以在无妨的情况下这样做,但我发现全局存储确实有几个缺点。

Props 初始化

全局存储是在 React 组件生命周期之外创建的,因此我们无法使用作为 prop 获得的值来初始化存储。对于全局存储,我们需要首先使用已知的默认状态创建它,然后将 props-to-storeuseEffect 同步:

const useBearStore = create((set) => ({// ⬇️ 用默认值初始化bears: 0,actions: {increasePopulation: (by) =>set((state) => ({ bears: state.bears + by })),removeAllBears: () => set({ bears: 0 }),},
}))const App = ({ initialBears }) => {//😕 将初始值写入我们的 StoreReact.useEffect(() => {useBearStore.set((prev) => ({ ...prev, bears: initialBears }))}, [initialBears])return (<main><RestOfTheApp /></main>)
}

除了不想写 useEffect 之外,这并不理想,原因有两个:

  1. 在效果生效之前,我们首先使用 bears: 0 渲染 <RestOfTheApp /> ,然后使用正确的 initialBears 再次渲染它。
  2. 我们并不是用 initialBears 来初始化我们的 Store,而是将其同步。因此,如果initialBears 发生变化,我们将看到更新也反映在我们的 Store 中。

可重用组件

并非所有 Store 都是单例,我们可以在应用程序中或在特定路线中使用一次。有时我们也希望 zustand 存储可重用组件。我能想到的一个过去的例子是设计系统中的一个复杂的多选择组组件。

它使用通过 React Context 传递的本地状态来管理选择的内部状态。当有五十个或更多的项目时,每当选择一个项目时,它就会变得缓慢。

如果这样的 zustand 存储是全局的,我们就无法在不共享和覆盖彼此状态的情况下多次实例化该组件。

React 上下文

有趣且讽刺的是,React Context 是这里的解决方案,因为使用 Context 作为状态管理工具首先导致了上述问题。这个想法是仅仅通过 React Context 共享存储实例,而不是存储值本身。

从概念上讲,这就是 React Query 对 <QueryClientProvider> 所做的事情,以及 redux 对它们的单个存储所做的事情。

因为 store 实例是不经常更改的静态单例,所以我们可以轻松地将它们放入 React Context 中,而不会导致重新渲染问题。

然后,我们仍然可以为将由 zustand 优化的 Store 创建订阅者。看起来是这样的:

import { createStore, useStore } from 'zustand'const BearStoreContext = React.createContext(null)const BearStoreProvider = ({ children, initialBears }) => {const [store] = React.useState(() =>createStore((set) => ({bears: initialBears,actions: {increasePopulation: (by) =>set((state) => ({ bears: state.bears + by })),removeAllBears: () => set({ bears: 0 }),},})))return (<BearStoreContext.Provider value={store}>{children}</BearStoreContext.Provider>)
}

这里的主要区别是我们没有像以前那样使用 create,这将为我们提供一个随时可用的钩子。

相反我们依赖于普通的 zustand 函数 createStore,它只会为我们创建一个 Store 。我们可以在任何我们想要的地方做到这一点——甚至在组件内部。

但是必须确保 Store 的创建只发生一次,我们可以使用 refs 来做到这一点,但我更喜欢 useState

因为我们在组件内创建了 store,所以我们可以关闭像 initialBears 这样的 props,并将它们作为真正的初始值传递给 createStore

useState 初始化函数仅运行一次,因此对 prop 的更新不会传递到存储。然后,我们获取 store 实例并将其传递给一个普通的 React Context。这里不再有任何具体的内容了。

之后,每当我们想从存储中选择一些值时,我们都需要使用该上下文。为此,我们需要将 storeselector 传递给我们可以从 zustand 获取的 useStore 钩子。这最好在自定义挂钩中抽象:

const useBearStore = (selector) => {const store = React.useContext(BearStoreContext)if (!store) {throw new Error('Missing BearStoreProvider')}return useStore(store, selector)
}

然后,我们可以像以前一样使用 useBearStore 钩子,并使用原子选择器导出自定义钩子:

export const useBears = () => useBearStore((state) => state.bears)

与创建全局存储相比,这需要编写更多的代码,但它解决了所有三个问题:

  1. 正如示例所示,我们现在可以使用 props 初始化我们的 store,因为我们是在 React 组件树中创建它的。
  2. 测试变得轻而易举,因为我们可以渲染一个包含 BearStoreProvider 的组件,或者我们可以自己渲染一个组件来进行测试。在这两种情况下,创建的存储将与测试完全隔离,因此无需在测试之间进行重置。
  3. 组件现在可以呈现 BearStoreProvider 为其子组件提供封装的 zustand 存储。我们可以在一个页面上根据需要多次渲染该组件,每个实例都有自己的存储,因此我们实现了可重用性。

因此,尽管 zustand 文档以不需要 Context Provider 来访问存储而自豪,但我认为知道如何将存储创建与 React Context 结合起来在需要封装和可重用性的情况下会非常方便。


http://www.ppmy.cn/devtools/14806.html

相关文章

三、fpga对完成过滤和校验的有效包数据进行有效像素提取、MATLAB对数据源进行处理与下发(完整实现pc机→显示器通信链路)

前言:上篇文章实现了MATLAB模拟发送UDP以太网协议数据包到fpga,能实现双沿数据→单沿数据转换,并将转换后的数据进行包过滤和crc校验,本篇内容要实现真正的从pc机下发视频数据,经过千兆以太网传输存储到fpga 的ddr3中,然后通过hdmi读出到显示屏上。 文章目录 一、模块设…

机器学习(二)之监督学习

前言&#xff1a; 上一节大概讲解了几种学习方式&#xff0c;下面几张就具体来讲讲监督学习的几种算法。 以下示例中和都是权重的意思&#xff01;&#xff01;&#xff01; 注&#xff1a;本文如有错误之处&#xff0c;还请读者指出&#xff0c;欢迎评论区探讨&#xff01; 1…

持有消防设施设计乙级资质可承接的业务范围

持有消防设施设计乙级资质的企业&#xff0c;其可承接的业务范围主要包括以下方面&#xff1a; 中型以下工业与民用建筑消防设施设计&#xff1a; 民用建筑&#xff1a;可承担单体建筑面积在2万平方米至4万平方米之间的中型民用建筑的消防设施工程专项设计&#xff0c;这类建筑…

19 使用MapReduce编程统计超市1月商品被购买的次数

首先将1月份的订单数据上传到HDFS上&#xff0c;订单数据格式 ID Goods两个数据字段构成 将订单数据保存在order.txt中&#xff0c;&#xff08;上传前记得启动集群&#xff09;。 打开Idea创建项目 修改pom.xml&#xff0c;添加依赖 <dependencies><dependency>…

如何调节电脑屏幕亮度?让你的眼睛更舒适!

电脑屏幕亮度的调节对于我们的视力保护和使用舒适度至关重要。不同的环境和使用习惯可能需要不同的亮度设置。可是如何调节电脑屏幕亮度呢&#xff1f;本文将介绍三种不同的电脑屏幕亮度调节方法&#xff0c;帮助您轻松调节电脑屏幕亮度&#xff0c;以满足您的需求。 方法1&…

微信小程序 实现手写签名(横屏签名板)

效果图&#xff1a; 业务需求&#xff1a;点击签字空白处&#xff0c;调起签字版&#xff08;横屏展示&#xff09;&#xff0c;手写完之后点击确定回显签字内容 签名是使用canvas实现的 签名确认之后生成的一个临时图片 后台接口逻辑是生成的图片先上传到文件服务器&#x…

opencv图片绘制图形-------c++

绘制图形 #include <opencv2/opencv.hpp> #include <opencv2/core.hpp> #include <filesystem>bool opencvTool::drawPolygon(std::string image_p, std::vector<cv::Point> points) {cv::Mat ima cv::imread(image_p.c_str()); // 读取图像&#xf…

图论基础知识 并查集/例题

并查集 学习记录自代码随想录 并查集可以解决的问题&#xff1a; 并查集常用来解决连通性问题。 判断两个元素是否在同一个集合里的时候&#xff0c;要想到用并查集。 并查集主要有两个功能&#xff1a; 1.将两个元素添加到一个集合中&#xff1b; 2.判断两个元素在不在同一…