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

相关文章

Nginx实现前端灰度发布

《Nginx实现前端灰度发布》灰度发布是一种重要的策略,它允许我们在不影响所有用户的情况下,逐步推出新功能或更新,通过灰度发布,我们可以测试新版本的稳定性和性能,下面就来介绍一下前端灰度发布的使用,感... 目录前言一、基于权重的流量分配二、基于 Cookie 的分流三、基于请求头的分流四、基于请求参数的分

基于Canvas的Html5多时区动态时钟实战代码

《基于Canvas的Html5多时区动态时钟实战代码》:本文主要介绍了如何使用Canvas在HTML5上实现一个多时区动态时钟的web展示,通过Canvas的API,可以绘制出6个不同城市的时钟,并且这些时钟可以动态转动,每个时钟上都会标注出对应的24小时制时间,详细内容请阅读本文,希望能对你有所帮助...

HTML5 data-*自定义数据属性的示例代码

《HTML5data-*自定义数据属性的示例代码》HTML5的自定义数据属性(data-*)提供了一种标准化的方法在HTML元素上存储额外信息,可以通过JavaScript访问、修改和在CSS中使用... 目录引言基本概念使用自定义数据属性1. 在 html 中定义2. 通过 JavaScript 访问3.

CSS模拟 html 的 title 属性(鼠标悬浮显示提示文字效果)

《CSS模拟html的title属性(鼠标悬浮显示提示文字效果)》:本文主要介绍了如何使用CSS模拟HTML的title属性,通过鼠标悬浮显示提示文字效果,通过设置`.tipBox`和`.tipBox.tipContent`的样式,实现了提示内容的隐藏和显示,详细内容请阅读本文,希望能对你有所帮助... 效

Redis实现RBAC权限管理

《Redis实现RBAC权限管理》本文主要介绍了Redis实现RBAC权限管理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1. 什么是 RBAC?2. 为什么使用 Redis 实现 RBAC?3. 设计 RBAC 数据结构

Flutter监听当前页面可见与隐藏状态的代码详解

《Flutter监听当前页面可见与隐藏状态的代码详解》文章介绍了如何在Flutter中使用路由观察者来监听应用进入前台或后台状态以及页面的显示和隐藏,并通过代码示例讲解的非常详细,需要的朋友可以参考下... flutter 可以监听 app 进入前台还是后台状态,也可以监听当http://www.cppcn

前端bug调试的方法技巧及常见错误

《前端bug调试的方法技巧及常见错误》:本文主要介绍编程中常见的报错和Bug,以及调试的重要性,调试的基本流程是通过缩小范围来定位问题,并给出了推测法、删除代码法、console调试和debugg... 目录调试基本流程调试方法排查bug的两大技巧如何看控制台报错前端常见错误取值调用报错资源引入错误解析错误

Vue中动态权限到按钮的完整实现方案详解

《Vue中动态权限到按钮的完整实现方案详解》这篇文章主要为大家详细介绍了Vue如何在现有方案的基础上加入对路由的增、删、改、查权限控制,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、数据库设计扩展1.1 修改路由表(routes)1.2 修改角色与路由权限表(role_routes)二、后端接口设计

Vue项目的甘特图组件之dhtmlx-gantt使用教程和实现效果展示(推荐)

《Vue项目的甘特图组件之dhtmlx-gantt使用教程和实现效果展示(推荐)》文章介绍了如何使用dhtmlx-gantt组件来实现公司的甘特图需求,并提供了一个简单的Vue组件示例,文章还分享了一... 目录一、首先 npm 安装插件二、创建一个vue组件三、业务页面内 引用自定义组件:四、dhtmlx

Vue ElementUI中Upload组件批量上传的实现代码

《VueElementUI中Upload组件批量上传的实现代码》ElementUI中Upload组件批量上传通过获取upload组件的DOM、文件、上传地址和数据,封装uploadFiles方法,使... ElementUI中Upload组件如何批量上传首先就是upload组件 <el-upl