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

相关文章

Linux内存泄露的原因排查和解决方案(内存管理方法)

《Linux内存泄露的原因排查和解决方案(内存管理方法)》文章主要介绍了运维团队在Linux处理LB服务内存暴涨、内存报警问题的过程,从发现问题、排查原因到制定解决方案,并从中学习了Linux内存管理... 目录一、问题二、排查过程三、解决方案四、内存管理方法1)linux内存寻址2)Linux分页机制3)

vue基于ElementUI动态设置表格高度的3种方法

《vue基于ElementUI动态设置表格高度的3种方法》ElementUI+vue动态设置表格高度的几种方法,抛砖引玉,还有其它方法动态设置表格高度,大家可以开动脑筋... 方法一、css + js的形式这个方法需要在表格外层设置一个div,原理是将表格的高度设置成外层div的高度,所以外层的div需要

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

详解如何在React中执行条件渲染

《详解如何在React中执行条件渲染》在现代Web开发中,React作为一种流行的JavaScript库,为开发者提供了一种高效构建用户界面的方式,条件渲染是React中的一个关键概念,本文将深入探讨... 目录引言什么是条件渲染?基础示例使用逻辑与运算符(&&)使用条件语句列表中的条件渲染总结引言在现代

详解Vue如何使用xlsx库导出Excel文件

《详解Vue如何使用xlsx库导出Excel文件》第三方库xlsx提供了强大的功能来处理Excel文件,它可以简化导出Excel文件这个过程,本文将为大家详细介绍一下它的具体使用,需要的小伙伴可以了解... 目录1. 安装依赖2. 创建vue组件3. 解释代码在Vue.js项目中导出Excel文件,使用第三

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

高效管理你的Linux系统: Debian操作系统常用命令指南

《高效管理你的Linux系统:Debian操作系统常用命令指南》在Debian操作系统中,了解和掌握常用命令对于提高工作效率和系统管理至关重要,本文将详细介绍Debian的常用命令,帮助读者更好地使... Debian是一个流行的linux发行版,它以其稳定性、强大的软件包管理和丰富的社区资源而闻名。在使用

vue解决子组件样式覆盖问题scoped deep

《vue解决子组件样式覆盖问题scopeddeep》文章主要介绍了在Vue项目中处理全局样式和局部样式的方法,包括使用scoped属性和深度选择器(/deep/)来覆盖子组件的样式,作者建议所有组件... 目录前言scoped分析deep分析使用总结所有组件必须加scoped父组件覆盖子组件使用deep前言

VUE动态绑定class类的三种常用方式及适用场景详解

《VUE动态绑定class类的三种常用方式及适用场景详解》文章介绍了在实际开发中动态绑定class的三种常见情况及其解决方案,包括根据不同的返回值渲染不同的class样式、给模块添加基础样式以及根据设... 目录前言1.动态选择class样式(对象添加:情景一)2.动态添加一个class样式(字符串添加:情

React实现原生APP切换效果

《React实现原生APP切换效果》最近需要使用Hybrid的方式开发一个APP,交互和原生APP相似并且需要IM通信,本文给大家介绍了使用React实现原生APP切换效果,文中通过代码示例讲解的非常... 目录背景需求概览技术栈实现步骤根据 react-router-dom 文档配置好路由添加过渡动画使用