通过示例解释 React 中的 Scoped Context
**React Context 是一个全局变量**
在 Javascript 中,变量的作用域在函数定义内。
通过示例解释 React 中的作用域上下文
React Context 通常被描述为一种管理全局状态的机制,充当可跨 React 组件树访问的共享变量。虽然这种描述是准确的,但它过于简化了 Context 的功能。在本文中,我们将深入探讨如何有效地限定 Context 的范围,确保它只在需要的地方使用,并避免不必要的重新渲染。
什么是 React Context?
React Context 提供了一种通过组件树传递数据的方法,而无需在每个级别手动传递 props。它是使用 `React.createContext` 创建的,由 `Provider` 和 `Consumer` 对组成。`Provider` 组件提供值,任何用 `Consumer` 或 `useContext` 钩子包装的组件都可以访问它。
这是一个基本的例子:
import React, { createContext, useContext } from "react"; const ThemeContext = createContext("light"); function App() { return (); } function Toolbar() { return ; } function ThemedButton() { const theme = useContext(ThemeContext); return ; } export default App;
在这个例子中,`ThemedButton` 可以访问 `ThemeContext.Provider` 提供的 `theme` 值,而无需通过 `Toolbar` 明确传递 props。
为什么要使用作用域上下文?
虽然 Context 功能强大,但滥用它可能会导致性能问题。当 `Context.Provider` 提供的值发生变化时,所有使用该上下文的组件都将重新渲染。在复杂的应用程序中,这可能会导致不相关的组件不必要的重新渲染。
Scoped Context 是指将 Context 的使用限制在组件树中真正需要的部分的做法。这种方法有助于保持性能,并使组件结构保持干净和易懂。
复合部件面临的挑战
考虑涉及复合组件的场景,例如 Radix Primitives 等库提供的组件。这些组件通常在内部使用 Context 来管理状态和交互。但是,当类似的组件组合在一起时可能会出现问题,导致上下文冲突。
基数原语示例
Radix Primitives 提供了高度可组合的 API,用于构建可访问的组件。以下是示例:
{/* note the alert trigger in dialog content */}
这里出现了一个问题,因为 `AlertDialog` 是 `Dialog` 的组合,并附加了满足 `AlertDialog` 要求的功能。这意味着 `AlertDialog.Root` 也是一个 `Dialog.Root`,因此它同时提供了 `DialogContext` 和 `AlertDialogContext`。
在此设置中,`AlertDialog.Trigger`(也是 `Dialog.Trigger`)可能会通过 `useContext(DialogContext)` 检索错误的上下文,最终会从 `Dialog.Root` 而不是 `AlertDialog.Root` 获取上下文。因此,单击 `AlertDialog.Trigger` 可能会切换 `Dialog.Content`,而不是按预期运行。
Scoped Context 解决方案
为了防止此类问题,Radix Primitives 使用了作用域上下文。作用域上下文确保 `AlertDialog.Trigger` 仅与 `AlertDialog` 部分交互,并且不会意外地从类似组合的组件中检索上下文。这是通过在内部创建新上下文并通过自定义 prop(例如 `__scopeDialog`)将其传递给 `Dialog` 组件来实现的。然后,`Dialog` 组件在其 `useContext` 调用中使用此作用域上下文,从而确保隔离。
来自 radix ui github repo 的源代码:
https://github.com/radix-ui/primitives/blob/dae8ef4920b45f736e2574abf23676efab103645/packages/react/dialog/src/Dialog.tsx#L69
以下是 Radix UI 中作用域上下文的工作方式的抽象:
作用域上下文的好处
如何应用于示例
在您的示例中: