React Context 是一个全局变量
在 Javascript 中,变量的作用域位于函数定义内。
React Context 通常被描述为一种管理全局状态的机制,充当可跨 React 组件树访问的共享变量。虽然这个描述是准确的,但它过于简化了 Context 的功能。在本文中,我们将深入探讨如何有效地确定 Context 的范围,确保仅在需要的地方使用它并避免不必要的重新渲染。
React Context 提供了一种通过组件树传递数据的方法,而无需在每个级别手动传递 props。它是使用 React.createContext 创建的,由 Provider 和 Consumer 对组成。 Provider 组件提供值,任何用 Consumer 或 useContext 钩子包装的组件都可以访问它。
这是一个基本示例:
import React, { createContext, useContext } from "react"; const ThemeContext = createContext("light"); function App() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar() { return <ThemedButton />; } function ThemedButton() { const theme = useContext(ThemeContext); return <button>{`Theme: ${theme}`}</button>; } export default App;
在此示例中,ThemedButton 可以访问 ThemeContext.Provider 提供的主题值,而无需通过 Toolbar 显式传递 props。
虽然 Context 很强大,但不加区别地使用它可能会导致性能问题。当 Context.Provider 提供的值发生更改时,使用该上下文的所有组件都将重新呈现。在复杂的应用程序中,这可能会导致不相关组件不必要的重新渲染。
Scoped Context 是指将 Context 的使用限制在组件树中真正需要它的部分的做法。这种方法有助于保持性能并保持组件结构干净且易于理解。
考虑涉及复合组件的场景,例如 Radix Primitives 等库提供的组件。这些组件通常在内部使用 Context 来管理状态和交互。然而,当相似的组件组合在一起时,可能会出现问题,导致上下文冲突。
Radix Primitives 提供了高度可组合的 API,用于构建可访问的组件。这是一个例子:
<AlertDialog.Root> <Dialog.Root> <Dialog.Trigger /> <Dialog.Content> <AlertDialog.Trigger /> {/* note the alert trigger in dialog content */} </Dialog.Content> </Dialog.Root> <AlertDialog.Content /> </AlertDialog.Root>
这里出现了一个问题,因为 AlertDialog 是 Dialog 的组合,具有满足 AlertDialog 要求的附加功能。这意味着AlertDialog.Root也是一个Dialog.Root,因此它同时提供了DialogContext和AlertDialogContext。
在此设置中,AlertDialog.Trigger(也是 Dialog.Trigger)可能会通过 useContext(DialogContext) 检索错误的上下文,最终得到来自 Dialog.Root 而不是 AlertDialog.Root 的上下文。因此,单击 AlertDialog.Trigger 可能会切换 Dialog.Content,而不是按预期运行。
为了防止此类问题,Radix Primitives 使用作用域上下文。作用域上下文确保 AlertDialog.Trigger 仅与 AlertDialog 部件交互,并且不会意外地从类似组成的组件中检索上下文。这是通过在内部创建一个新上下文并通过自定义属性(例如 __scopeDialog)将其传递给 Dialog 组件来实现的。然后,Dialog 组件在其 useContext 调用中使用此作用域上下文,以确保隔离。
来自 radix ui github 存储库的源代码:
https://github.com/radix-ui/primitives/blob/dae8ef4920b45f736e2574abf23676efab103645/packages/react/dialog/src/Dialog.tsx#L69
范围创建:createScope 实用程序为每个组件或复合组件生成唯一的命名空间。这确保了每组上下文都是隔离的并且不会与其他上下文发生冲突。
import React, { createContext, useContext } from "react"; const ThemeContext = createContext("light"); function App() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar() { return <ThemedButton />; } function ThemedButton() { const theme = useContext(ThemeContext); return <button>{`Theme: ${theme}`}</button>; } export default App;
作用域提供者:创建上下文时,它们与作用域绑定。这将提供者和消费者绑定到同一个命名空间。
<AlertDialog.Root> <Dialog.Root> <Dialog.Trigger /> <Dialog.Content> <AlertDialog.Trigger /> {/* note the alert trigger in dialog content */} </Dialog.Content> </Dialog.Root> <AlertDialog.Content /> </AlertDialog.Root>
消费者隔离:作用域挂钩,如 useDialogScope,确保消费者仅访问其预期范围内的上下文。
import { createScope } from '@radix-ui/react-context'; const [createDialogContext, useDialogScope] = createScope('Dialog');
上下文冲突预防:通过确定上下文范围,AlertDialog.Trigger 等组件始终可以找到其关联的上下文 (AlertDialogContext),即使嵌套在其他上下文中也是如此。
灵活组合:作用域上下文支持灵活安全的组件组合,确保交互保持可预测。
可重用性:开发人员可以在不同范围内重用通用组件(例如 Dialog.Trigger),无需修改。
在您的示例中:
AlertDialog.Root 创建一个有作用域的 AlertDialogContext,封装其状态和交互。
嵌套的 Dialog.Root 和 AlertDialog.Trigger 共存而不会发生冲突,因为它们各自引用其各自的作用域上下文。
此设计模式是 Radix UI 的一个关键功能,可确保复杂的组件层次结构无缝工作,而不会出现意外行为。
https://dev.to/romaintrotard/use-context-selector-demystified-4f8e
https://github.com/radix-ui/primitives
https://react.dev/reference/react/createContext
以上是用例子解释 React 中的作用域上下文的详细内容。更多信息请关注PHP中文网其他相关文章!