React Query 和 React Context

server/2024/10/17 14:53:05/


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.ppmy.cn/server/132507.html

相关文章

【NVIDIA NIM 黑客松训练营】文生图小应用

项目简介 以下是一个使用 NIM 平台的生成式 AI模型构建的简单 Demo。 Demo使用了模型meta / llama3-70b-instruct和nvidia / consistory&#xff0c;首先是优化了模型meta / llama3-70b-instruct默认的英文输出&#xff0c;使其对中文用户更友好&#xff1b;其次根据用户输入判…

华为HCIP考试改革,实验部分重要性提升,备考需知!

在当今这个行情复杂多变的时代&#xff0c;网络工程师的技能水平评估标准愈发重要&#xff0c;而认证无疑成为了其中关键的衡量尺度之一。 最近&#xff0c;华为认证领域内部传出了一则颇具影响力的消息&#xff1a;HCIP 认证即将增加实验考试&#xff01;想必不少朋友都已有所…

基于springboot实习管理系统

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 系统展示 【2024最新】基于JavaSpringBootVueMySQL的&#xff0c;前后端分离。 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;…

【JVM】一文详解类加载器

文章目录 类加载器的概述类加载器的分类启动类加载器(Bootstrap ClassLoader )扩展类型加载器(ExClassLoader)系统类加载器(Application ClassLoader )总结 双亲委派机制概念双亲委派机制的优势 ClassLoaderfindClassdefineClassloadClass&#xff0c;findClass&#xff0c;def…

使用DeepKE训练命名实体识别模型DEMO(官方DEMO)

使用DeepKE训练命名实体识别模型DEMO&#xff08;官方DEMO&#xff09; 说明&#xff1a; 首次发表日期&#xff1a;2024-10-10DeepKE资源&#xff1a; 文档&#xff1a; https://www.zjukg.org/DeepKE/网站&#xff1a; http://deepke.zjukg.cn/cnschema&#xff1a; http:/…

Java设计模式梳理:行为型模式(策略,观察者等)

行为型模式 行为型模式关注的是各个类之间的相互作用&#xff0c;将职责划分清楚&#xff0c;使得我们的代码更加地清晰。 策略模式 策略模式太常用了&#xff0c;所以把它放到最前面进行介绍。它比较简单&#xff0c;我就不废话&#xff0c;直接用代码说事吧。 下面设计的…

用示波器观测RC一阶电路零输入响应是否激励必须是方波信号

概述 RC一阶电路是一种简单但非常重要的电路&#xff0c;广泛应用于滤波、信号处理和时间常数分析等领域。在研究RC电路的动态特性时&#xff0c;零输入响应&#xff08;Natural Response&#xff09;是一项关键内容。本文将详细解析用示波器观测RC一阶电路零输入响应时&#…

Spring Boot助力B2B医疗平台病历数据交换

第1章绪论 计算机已经从科研院所&#xff0c;大中型企业&#xff0c;走进了平常百姓家&#xff0c;Internet遍及世界各地&#xff0c;在网上能够用计算机进行文字草拟、修改、打印清样、文件登陆、检索、综合统计、分类、数据库管理等&#xff0c;用科学的方法将无序的信息进行…