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中文網其他相關文章!