React Query 和 React Context

news2024/12/27 11:11:03


React Query最佳特性之一是你可以在组件树中的任何位置使用查询:你的 <ProductTable> 组件可以在其需要的地方自带数据获取:

function ProductTable() {
  const productQuery = useProductQuery()

  if (productQuery.data) {
    return <table>...</table>
  }

  if (productQuery.isError) {
    return <ErrorMessage error={productQuery.error} />
  }

  return <SkeletonLoader />
}

它使得 ProductTable组件解耦且独立:它负责读取自己的依赖:产品数据。如果这些数据已经在缓存中,那么很好,我们只需要读取它。如果没有,我们就去获取数据。我们可以在 React Server Components 中看到类似的模式。它们也允许我们在组件内部获取数据。不再需要在有状态和无状态,或智能和哑组件之间进行任意划分。

所以在需要的地方直接在组件内部获取数据是非常有用的。我们可以直接把 ProductTable组件移动到应用程序中的任何位置,它都能正常工作。

自包含性

要让一个组件是自主的,这意味着它必须处理查询数据不可用(尚未加载)的情况,特别是:加载和错误状态。这对我们的 <ProductTable>组件来说并不是什么大问题,因为通常,当它第一次加载时,它实际上会显示 <SkeletonLoader />

但是在很多其他情况下,我们只是想从查询的某些部分读取一些信息,而我们知道查询已经在树的上方使用过。例如,我们可能有一个包含登录用户信息的 userQuery

export const useUserQuery = (id: number) => {
  return useQuery({
    queryKey: ['user', id],
    queryFn: () => fetchUserById(id),
  })
}
export const useCurrentUserQuery = () => {
  const id = useCurrentUserId()

  return useUserQuery(id)
}

我们可能会在组件树的早期使用这个查询,以检查登录用户的权限,并且它可能进一步决定我们是否可以看到页面。这是我们希望在页面上的任何地方都能获取的重要信息。

现在在树的更下方,我们可能有一个想要显示 userName的组件,这个信息我们可以通过useCurrentUserQuery 钩子获取:

function UserNameDisplay() {
  const { data } = useCurrentUserQuery()
  return <div>User: {data.userName}</div>
}

TypeScript不会允许我们这样做,因为data可能是未定义的。但我们知道得更好 - 它不可能是未定义的,因为在我们的情况下,如果查询尚未在树的上方初始化,UserNameDisplay就不会被渲染。

我们要让 TypeScript直接使用 data!.userName 吗,因为我们知道它会被定义?我们是要安全起见使用 data?.userName(在这里可能,但在其他情况下可能不容易实现)?我们是否只需添加一个保护:if (!data) return null?还是我们要在调用 useCurrentUserQuery的所有25个地方添加正确的加载和错误处理?

隐含的依赖

我们的问题来自于我们有一个隐含的依赖:一个只存在于我们头脑中的依赖,在我们对应用程序结构的知识中,但它在代码中并不可见。

尽管我们知道可以安全地调用 useCurrentUserQuery而无需检查数据是否未定义,但任何静态分析都无法验证这一点。自己可能在3个月后也不再记得这一点。

最危险的是,现在可能是对的,但将来可能不再正确。我们可能会决定在应用程序的其他地方渲染另一个 UserNameDisplay 实例,在那里我们可能没有用户数据缓存,或者我们可能有条件地缓存用户数据,例如,如果我们之前访问过不同的页面。

这与 <ProductTable> 组件完全相反:它变得容易出错并且难以重构。我们不会期望 UserNameDisplay 组件因为移动了一些看似不相关的组件而崩溃...

明确依赖关系

解决方案当然是让依赖关系明确。而使用 React Context 是最好的方法:

React Context

关于 React Context 存在一些误解,我们先把这些弄清楚:React Context 不是一个状态管理器。当它与useStateuseReducer结合使用时,可能看起来是个不错的状态管理解决方案:React Context是一个依赖注入工具。它允许你定义你的组件需要哪些“东西”,并且由任何父组件负责提供这些信息。

这在概念上与属性传递(prop-drilling)相同,属性传递是通过多个层级传递属性的过程。Context允许你做同样的事情:获取一些值并将其作为属性传递给子组件,只是你可以省去几个中间层级:

使用 context,你只需跳过中间层。在useCurrentUserQuery示例中,它可以帮助我们明确依赖关系:不再在所有需要跳过数据可用性检查的组件中直接读取 useCurrentUserQuery,而是从React Context中读取。而这个context将由实际进行第一次检查的父组件填充:

const CurrentUserContext = React.createContext<User | null>(null)

export const useCurrentUserContext = () => {
  return React.useContext(CurrentUserContext)
}

export const CurrentUserContextProvider = ({
  children,
}: {
  children: React.ReactNode
}) => {
  const currentUserQuery = useCurrentUserQuery()

  if (currentUserQuery.isLoading) {
    return <SkeletonLoader />
  }

  if (currentUserQuery.isError) {
    return <ErrorMessage error={currentUserQuery.error} />
  }

  return (
    <CurrentUserContext.Provider value={currentUserQuery.data}>
      {children}
    </CurrentUserContext.Provider>
  )
}

在这里,我们获取currentUserQuery并将其结果数据放入 React Context中(通过提前消除加载和错误状态)。然后我们可以在子组件中安全地从该 context中读取,例如 UserNameDisplay组件:

function UserNameDisplay() {
  const data = useCurrentUserContext()
  return <div>User: {data.username}</div>
}

这样,我们就明确了隐含的依赖关系(我们知道数据已经在树的上方被获取)。每当有人查看UserNameDisplay时,他们会知道需要从 CurrentUserContextProvider提供数据。这是你在重构时可以记住的事情。如果你改变了 Provider的渲染位置,你也会知道这将影响所有使用该 context 的子组件。这是你无法知道的,当一个组件只是使用查询时——因为查询通常在整个应用程序中是全局的,数据可能存在也可能不存在。

TypeScript

TypeScript 依然不太喜欢这种方式,因为 React Context设计上也适用于没有 Provider的情况,在这种情况下它会给你 Context 的默认值,在我们的例子中是 null。由于我们不希望 useCurrentUserContext在不在 Provider中时工作,我们可以在自定义钩子中添加一个不变量:

export const useCurrentUserContext = () => {
  const currentUser = React.useContext(CurrentUserContext)
  if (!currentUser) {
    throw new Error('CurrentUserContext: No value provided')
  }

  return currentUser
}

这种方法确保了如果我们在错误的位置意外访问 useCurrentUserContext,我们会快速失败并得到一个良好的错误信息。这样,TypeScript将为我们的自定义钩子推断出User类型的值,因此我们可以安全地使用它并访问其属性。

状态同步

你可能会想:这不是“状态同步”吗——将一个值从 React Query复制到另一种状态分发方法?

来源依然是查询。除了 Provider之外,没有其他方法可以改变context值,Provider 将始终反映查询的最新数据。这里没有任何东西被复制,也没有任何东西可能会不同步。从 React Query作为属性传递数据给子组件也不是“状态同步”,而且由于context类似于属性传递,这也不是“状态同步”。

请求瀑布

没有什么是没有缺点的,这种技术也不例外。具体来说,它可能会产生网络瀑布,因为你的组件树会在 Provider处停止渲染,因此子组件不会被渲染,无法发出网络请求,即使它们是无关的。

考虑这种方法用于子树中必需的数据:用户信息是一个很好的例子,因为如果没有这些数据我们可能不知道该渲染什么。

Suspense

谈到 Suspense:是的,你可以用React Suspense实现类似的架构,并且它也有同样的缺点:潜在的请求瀑布,
一个问题是,在当前的主要版本(v4)中,对查询使用 suspense: true 不会对 data 进行类型收窄,因为还有其他方式可以禁用查询并使其不运行。

然而,自 v5 起,有一个显式的useSuspenseQuery钩子,在组件渲染时数据被保证是已定义的。这样,我们可以这样做:

function UserNameDisplay() {
  const { data } = useSuspenseQuery(...)
  return <div>User: {data.username}</div>
}

这样 TypeScript就会对它满意。🎉



喜欢的朋友记得点赞、收藏、关注哦!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2211342.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI跟踪报道第60期-新加坡内哥谈技术-本周AI新闻: Tesla展示Robotaxi和AI在企业级运用的推进

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

前端开发笔记-- 黑马程序员4

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 css 三角写法用户界面鼠标样式取消表单轮廓vertical-align文本溢出 html5 新标签多媒体标签视频标签![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d85d…

Linux升级openssl版本

Linux升级openssl版本 服务器编译依赖库检查 # yum -y install gcc gcc-c make libtool zlib zlib-devel版本检测 # openssl version OpenSSL 1.0.1e-fips 11 Feb 2013 # ssh -V OpenSSH_6.6.1p1, OpenSSL 1.0.1e-fips 11 Feb 2013下载openssl 地址&#xff1a;https://www.o…

Android中的内容提供者

目录 1.创建内容提供者 1--手动创建一个Android应用程序 2--创建自定义的内容提供者 2.访问其他应用程序 1. 解析URI 2. 查询数据 3. 遍历查询结果 3)案例:读取手机通信录 1.声明权限 2.activity_main.xml文件内容 3.my_phone_list.xml文件内容 4.定义PhoneInfo实体 5.定义MyPh…

西门子S7-200 SMART选型指南之高级功能

介绍了S7-200 SMART控制器的高级功能。用户可以了解到控制器所支持的通信功能、运动控制功能、PID功能和高速计数器能力。这使用户能够评估控制器是否满足其应用的功能要求。 S7-200 SMART 功能 S7-200smart传承了S7-200 系列CPU经典编程理念&#xff0c;可以实现基本编程及高…

CISSP官方模拟测试题

源于CISSP官方习题集最新第4版第9章&#xff08;ISC2 CISSP Certified Information Systems Security Professional Official Practice Tests Fourth Edition, Chapter 9: Practice Test 1&#xff09;。 中英文对照&#xff0c;限时150分钟考试&#xff0c;顺序作答不能回退&…

【大数据技术基础 | 实验二】Linux基础:常用基本命令和文件操作

文章目录 一、实验目的二、实验要求三、实验环境四、常用基本命令1、验证cd和pwd命令2、验证ls命令3、验证mkdir命令4、验证cp、mv和rm命令 五、Linux文件操作1、验证touch命令2、验证cat命令3、验证more命令 六、实验心得 一、实验目的 学会linux常用命令(cd,ls,pwd,mkdir,rm…

STM32 SPI通信 F407外设控制器 W25Q64

SPI介绍 SPI&#xff1a;串行外部设备接口 --- 通信接口 特点&#xff1a;高速同步串行全双工 --- 40MHz 场景&#xff1a;存储器 OLED 无线通信 传感器 硬件连线&#xff1a; …

docker安装elasticsearch(es)+kibana

目录 docker安装elasticsearch 一.准备工作 1.打开docker目录 2.创建elasticsearch目录 3.打开elasticsearch目录 4.拉取elasticsearch镜像 5.检查镜像 二.挂载目录 1.创建数据挂载目录 2.创建配置挂载目录 3.创建插件挂载目录 4.权限授权 三.编辑配置 1.打开con…

优先算法1--双指针

“一念既出&#xff0c;万山无阻。”加油陌生人&#xff01; 目录 1.双指针--移动零 2.双指针-复写零 ok&#xff0c;首先在学习之前&#xff0c;为了方便大家后面的学习&#xff0c;我们这里需要补充一个知识点&#xff0c;我这里所谓的指针&#xff0c;不是之前学习的带有…

RISC-V笔记——语法依赖

1. 前言 Memory consistency model定义了使用Shared memory(共享内存)执行多线程(Multithread)程序所允许的行为规范。RISC-V使用的内存模型是RVWMO(RISC-V Weak Memory Ordering)&#xff0c;该模型旨在为架构师提供更高的灵活性&#xff0c;以构建高性能可拓展的设计&#x…

51单片机的土壤湿度检测控制系统【proteus仿真+程序+报告+原理图+演示视频】

1、主要功能 该系统由AT89C51/STC89C52单片机LCD1602显示模块土壤湿度传感器继电器按键、蜂鸣器、LED等模块构成。适用于智能浇花浇水系统、花卉灌溉系统等相似项目。 可实现功能: 1、LCD1602实时显示土壤湿度 2、土壤湿度传感器&#xff08;滑动变阻器模拟&#xff09;采集…

鸿蒙--商品列表

这里主要利用的是 List 组件 相关概念 Scroll:可滚动的容器组件,当子组件的布局尺寸超过父组件的视口时,内容可以滚动。List:列表包

gitlab保护分支设置

版本&#xff1a;gitlab10.2.2 一旦设置master分支被保护&#xff0c;除了管理员之外的任何用户都无法直接向master提交代码&#xff0c;只要提交代码就会报错 # git push -u origin master Total 0 (delta 0), reused 0 (delta 0) remote: GitLab: You are not allowed to pu…

前端优化,解决页面加载慢

问题&#xff1a;vue项目使用vite打包后&#xff0c;部署在nginx服务器上&#xff0c;页面上访问时很慢&#xff0c;发现有个js文件很大导致加载很慢 先说结论&#xff1a; 方式时间未优化前21s开启压缩&#xff08;6级&#xff09;6s去掉大依赖&#xff08;flowable&#xf…

【CTF-SHOW】 web入门 web11-域名隐藏信息 【详-域名】

这道题的主要思路是通过DNS查询&#xff08;或利用题目中所给的网址直接查询&#xff09;指定域名解析以获得txt记录 1.什么是域名&#xff1f; 域名&#xff08;Domain Name&#xff09; 是互联网上用来标识网站或网络服务的名字&#xff0c;它是一个人类易于记忆和使用的地…

InfluxDB快速掌握

文章目录 1、InfluxDB简介2、InfluxDB数据结构3、InfluxDB存储架构4、InfluxDB基本操作1_数据库操作2_数据表操作3_数据保存策略4_数据查询操作 5、存储引擎6、总结 1、InfluxDB简介 时序数据库是近几年一个特殊的概念&#xff0c;与传统的Mysql关系型数据库相比&#xff0c;它…

算法:560.和为k的子数组

题目 链接:leetcode链接 思路分析&#xff08;前缀和&#xff09; 注意&#xff1a;我们前面讲过滑动窗口可以处理子数组、子串等问题&#xff0c; 但是在这道题目里面注意数据范围 -1000 < nums[i] < 1000 nums[i]可正可负&#xff0c;区间的和没有单调性&#xff0c;使…

Python案例 |地图绘制及分级着色

1、分级着色地图 分级着色地图常用于可视化地理数据&#xff0c;比如人口密度、经济数据、气候变化等。其原理是使用颜色或阴影的渐变来表示不同区域(如国家、省份、城市等)中的数据差异。例如&#xff0c;地图上的每个区域根据其代表的数值被着色&#xff0c;通常数值越大&am…

React Leaflet + React Pixi:双倍的快乐,我全都要

一篇实用性的文章&#xff0c;记录一下最近在自娱自乐使用 Leaflet 和 PixiJS 的过程中整的一个有意思的活&#xff0c;帮助我们使用 React 声明式的语法在 Leaflet 的图层上使用 PixiJS 绘图。 如果你对这些库和它们的用途都已有所了解&#xff0c;只想直接看代码的话&#xf…