Zustand 和 React 上下文状态管理

这篇具有很好参考价值的文章主要介绍了Zustand 和 React 上下文状态管理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

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

Zustand 和 React 上下文状态管理,react.js,前端,javascript,node.js,ecmascript,前端框架

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 }) => {
  //😕 将初始值写入我们的 Store
  React.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 结合起来在需要封装和可重用性的情况下会非常方便。文章来源地址https://www.toymoban.com/news/detail-858059.html

到了这里,关于Zustand 和 React 上下文状态管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 无限上下文,多级内存管理!突破ChatGPT等大语言模型上下文限制

    目前,ChatGPT、Llama 2、文心一言等主流大语言模型,因技术架构的问题上下文输入一直受到限制,即便是Claude 最多只支持10万token输入,这对于解读上百页报告、书籍、论文来说非常不方便。 为了解决这一难题,加州伯克利分校受操作系统的内存管理机制启发,提出了MemGPT。

    2024年02月06日
    浏览(47)
  • JS引擎中的线程,事件循环,上下文

      线程 浏览器中有哪些进程呢? 1.浏览器进程:浏览器的主进程,负责浏览器的界面界面显示,与用户交互,网址栏输入、前进、后退,以及页面的创建和销毁。 2.渲染进程(浏览器内核):默认一个tab页面一个渲染进程,主要的作用为页面渲染,脚本执行,事件处理等。 3.

    2024年02月08日
    浏览(82)
  • 【Python】Flask上下文管理

    current_app 类型:用用上下文的代理对象 主要用途:提供对当前激活的Flask应用实例的访问。通常访问应用配置,注册的蓝图,应用级别的数据等等 使用场景:在视图函数,错误处理器或者其他任何需要访问应用配置和属性的地方 实际数据实例:‘current_app.config[‘DEBUG’]’可

    2024年03月18日
    浏览(52)
  • Python函数-上下文管理器

    上下文管理协议(Context Management Protocol):包含方法 enter () 和 exit (),支持该协议的对象要实现这两个方法。 上下文管理器(Context Manager):支持上下文管理协议的对象,这种对象实现了__enter__() 和 exit () 方法。 上下文管理器定义执行 with 语句时要建立的运行时上下文,负

    2024年02月12日
    浏览(37)
  • Python_上下文管理器

    目录 上下文管理器类 多上下文管理器 contextmanager实现上下文管理器 上下文管理器(context manager)是 Python 编程中的重要概念,用于规定某个对象的使用范围。一旦进入或者离开该使用范围,会有特殊操作被调用 (比如为对象分配或者释放内存)。它的语法形式是with...as... 为了确

    2024年02月11日
    浏览(46)
  • 【转】JavaScript 执行上下文——JS 的幕后工作原理

    转自译文: JavaScript 执行上下文——JS 的幕后工作原理 。 译文中图片不显示,要结合原文看,看着不方便。整理了一份含图片的。所以有了此篇的转载,以方便阅读。 以下是正文: 原文:JavaScript Execution Context – How JS Works Behind The Scenes,作者:Victor Ikechukwu 所有JavaScript代码

    2024年01月20日
    浏览(32)
  • python 上下文管理器 with 语句

    上下文管理器 with 语句可以自动地分配和释放资源。 这篇文章中介绍了文件读写的方法,每次读取文件时要使用 open() 函数打开文件,使用结束后要用 close() 函数关闭文件。有时,我们会忘记关闭文件,或者打开了许多文件占用太多资源。使用上下文管理器 with 语句可以方便

    2023年04月09日
    浏览(47)
  • js中this关键字的作用和如何改变其上下文

    一、this 的作用 JavaScript 中的 this 引用了所在函数正在被调用时的对象。在不同的上下文中, this 的指向会发生变化。 在全局上下文中, this 指向全局对象(在浏览器中是 window 对象,在 Node.js 中是 global 对象)。 在函数中, this 指向调用该函数的对象。如果该

    2024年02月07日
    浏览(32)
  • pymysql 上下文管理器控制事务提交和回滚

    示例: 从上面例子看出, 在with结构进入时,事务开始begin 在with结构退出时,会根据是否产生异常进行commit和rollback操作,并关闭连接

    2024年01月25日
    浏览(48)
  • Python高级语法:with语句和上下文管理器

    1.文件操作说明: ①文件使用完后必须关闭。 ②因文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的。 例如:  2. 存在的安全隐患: ① 由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。 例如: 运行结果: 3.try…except…

    2024年02月04日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包