Zustand 和 React 上下文状态管理

2024-04-21 16:12

本文主要是介绍Zustand 和 React 上下文状态管理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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 结合起来在需要封装和可重用性的情况下会非常方便。

这篇关于Zustand 和 React 上下文状态管理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/923504

相关文章

Vue3组件中getCurrentInstance()获取App实例,但是返回null的解决方案

《Vue3组件中getCurrentInstance()获取App实例,但是返回null的解决方案》:本文主要介绍Vue3组件中getCurrentInstance()获取App实例,但是返回nu... 目录vue3组件中getCurrentInstajavascriptnce()获取App实例,但是返回n

JS+HTML实现在线图片水印添加工具

《JS+HTML实现在线图片水印添加工具》在社交媒体和内容创作日益频繁的今天,如何保护原创内容、展示品牌身份成了一个不得不面对的问题,本文将实现一个完全基于HTML+CSS构建的现代化图片水印在线工具... 目录概述功能亮点使用方法技术解析延伸思考运行效果项目源码下载总结概述在社交媒体和内容创作日益频繁的

前端CSS Grid 布局示例详解

《前端CSSGrid布局示例详解》CSSGrid是一种二维布局系统,可以同时控制行和列,相比Flex(一维布局),更适合用在整体页面布局或复杂模块结构中,:本文主要介绍前端CSSGri... 目录css Grid 布局详解(通俗易懂版)一、概述二、基础概念三、创建 Grid 容器四、定义网格行和列五、设置行

python uv包管理小结

《pythonuv包管理小结》uv是一个高性能的Python包管理工具,它不仅能够高效地处理包管理和依赖解析,还提供了对Python版本管理的支持,本文主要介绍了pythonuv包管理小结,具有一... 目录安装 uv使用 uv 管理 python 版本安装指定版本的 Python查看已安装的 Python

前端下载文件时如何后端返回的文件流一些常见方法

《前端下载文件时如何后端返回的文件流一些常见方法》:本文主要介绍前端下载文件时如何后端返回的文件流一些常见方法,包括使用Blob和URL.createObjectURL创建下载链接,以及处理带有C... 目录1. 使用 Blob 和 URL.createObjectURL 创建下载链接例子:使用 Blob

Vuex Actions多参数传递的解决方案

《VuexActions多参数传递的解决方案》在Vuex中,actions的设计默认只支持单个参数传递,这有时会限制我们的使用场景,下面我将详细介绍几种处理多参数传递的解决方案,从基础到高级,... 目录一、对象封装法(推荐)二、参数解构法三、柯里化函数法四、Payload 工厂函数五、TypeScript

SpringSecurity JWT基于令牌的无状态认证实现

《SpringSecurityJWT基于令牌的无状态认证实现》SpringSecurity中实现基于JWT的无状态认证是一种常见的做法,本文就来介绍一下SpringSecurityJWT基于令牌的无... 目录引言一、JWT基本原理与结构二、Spring Security JWT依赖配置三、JWT令牌生成与

基于Python和MoviePy实现照片管理和视频合成工具

《基于Python和MoviePy实现照片管理和视频合成工具》在这篇博客中,我们将详细剖析一个基于Python的图形界面应用程序,该程序使用wxPython构建用户界面,并结合MoviePy、Pill... 目录引言项目概述代码结构分析1. 导入和依赖2. 主类:PhotoManager初始化方法:__in

Vue3使用router,params传参为空问题

《Vue3使用router,params传参为空问题》:本文主要介绍Vue3使用router,params传参为空问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录vue3使用China编程router,params传参为空1.使用query方式传参2.使用 Histo

关于WebSocket协议状态码解析

《关于WebSocket协议状态码解析》:本文主要介绍关于WebSocket协议状态码的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录WebSocket协议状态码解析1. 引言2. WebSocket协议状态码概述3. WebSocket协议状态码详解3