React 的 Context API 簡介

已發表: 2022-03-10
快速總結↬在本文中,您將學習如何使用 React 的 Context API,它允許您在 React 應用程序中管理全局應用程序狀態,而無需借助 props 鑽取。

對於本教程,您應該對鉤子有一個公平的了解。 不過,在開始之前,我將簡要討論它們是什麼以及我們將在本文中使用的鉤子。

根據反應文檔​​:

Hooks是 React 16.8 中的新增功能。 它們讓你無需編寫類就可以使用狀態和其他 React 特性。”

這基本上就是 React 鉤子的內容。 它允許我們在功能組件中使用狀態、引用和其他 React 特性。

讓我們討論一下我們將在本文中遇到的兩個鉤子。

useState鉤子

useState 鉤子允許我們在功能組件中使用狀態useState鉤子將狀態的初始值作為唯一參數,並返回一個包含兩個元素的數組。 第一個元素是我們的狀態變量,第二個元素是一個函數,我們可以在其中使用更新狀態變量的值。

讓我們看一下下面的例子:

 import React, {useState} from "react"; function SampleComponent(){ const [count, setCount] = useState(0); }

這裡, count是我們的狀態變量,它的初始值為0 ,而setCount是一個函數,我們可以使用它來更新 count 的值。

useContext鉤子

我將在本文後面討論這個問題,但這個鉤子基本上允許我們使用上下文的值。 這實際上意味著什麼將在本文後面變得更加明顯。

紗線工作區

Yarn 工作區讓您可以使用單一存儲庫 (monorepo) 來組織項目代碼庫。 React 是一個很好的開源項目示例,它是 monorepo 並使用 Yarn 工作區來實現該目的。 閱讀相關文章 →

跳躍後更多! 繼續往下看↓

為什麼我們需要上下文 API?

我們想為我們的 React 應用構建一個“主題切換器”組件,它可以在亮模式和暗模式之間切換。 每個組件都必須能夠訪問當前的主題模式,以便可以相應地設置它們的樣式。

通常,我們會通過 props 為所有組件提供當前主題模式,並使用state更新當前主題:

 import React from "react"; import ReactDOM from "react-dom"; function App() { return ( <div> <Text theme= "blue" /> <h1>{theme}</h1> </div> ); } function Text({theme}) { return( <h1 style = {{ color: `${theme}` }}>{theme}</h1> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);

在上面的代碼示例中,我們創建了一個呈現h1元素的文本組件。 h1元素的顏色取決於當前的主題模式。 目前,主題是藍色。 我們可以使用statebluered主題之間切換。

我們將使用useState鉤子創建一個名為“主題”的狀態。 useState鉤子將返回主題的當前值和一個我們可以用來更新主題的函數。

所以,讓我們創建我們的主題狀態:

 const [theme, setTheme] = React.useState("blue");

我們還將向我們的App組件添加一個按鈕元素。 此按鈕將用於切換主題,它需要一個單擊事件處理程序。 所以,讓我們像這樣編寫點擊事件處理程序:

 const onClickHandler = () => { setTheme(); }

現在,如果當前主題是Blue ,我們想將新主題設置為Red ,反之亦然。 除了使用if語句,更方便的方法是藉助 JavaScript 中的三元運算符。

 setTheme( theme === "red"? "blue": "red");

所以現在,我們已經編寫了我們的onClick處理程序。 讓我們將此按鈕元素添加到App組件中:

 <button onClick = {onClickHandler}>Change theme</button>

讓我們也將 Text 組件的主題 props 的值更改為主題狀態。

 <Text theme={theme}/>

現在,我們應該有這個:

 import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; function App() { const[theme, setTheme] = React.useState("red"); const onClickHandler = () => { setTheme( theme === "red"? "blue": "red"); } return ( <div> <Text theme={theme}/> <button onClick = {onClickHandler}>Change theme</button> </div> ); } function Text({theme}) { return( <h1 style = {{ color: `${theme}` }}>{theme}</h1> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);

我們現在可以在兩個主題之間切換。 但是,如果這是一個更大的應用程序,則很難在深度嵌套的組件中使用該主題,並且代碼變得笨拙。

介紹上下文 API

讓我介紹一下 Context API。 根據 React 文檔:

“上下文提供了一種通過組件樹傳遞數據的方法,而無需在每個級別手動傳遞道具。”

對於更深入的定義,它為您提供了一種使特定數據可用於整個組件樹中的所有組件的方法,無論該組件的嵌套有多深。

讓我們看一下這個例子:

 const App = () => { return( <ParentComponent theme = "light"/> ); } const ParentComponent = (props) => ( <Child theme = {props.theme} /> ) const Child = (props) => ( <Grandchild theme = {props.theme} /> ) const Grandchild = (props) => ( <p>Theme: {props.theme}</p> )

在上面的示例中,我們使用ParentComponent中名為theme的道具指定了應用程序主題。 我們必須將該 props 傳遞給組件樹下的所有組件,以將其放在需要的地方,即GrandChild組件。 ChildComponent與主題道具無關,只是用作中介。

現在,想像一下GrandChild組件比上面的例子嵌套得更深。 我們必須像這裡一樣傳遞主題道具,這很麻煩。 這就是Context解決的問題。 使用Context ,組件樹中的每個組件都可以訪問我們決定放入上下文中的任何數據。

讓我們從Context開始

是時候複製我們在文章開頭使用 Context API 構建的主題切換按鈕了。 這一次,我們的主題切換器將是一個單獨的組件。 我們將構建一個ThemeToggler組件,它使用Context切換我們的 React 應用程序的主題。

首先,讓我們初始化我們的 React 應用程序。 (我更喜歡使用create-react-app但你可以使用任何你喜歡的方法。)

初始化 React 項目後,在/src文件夾中創建一個名為ThemeContext.js的文件。 如果需要,您還可以創建一個名為/context的文件夾並將您的ThemeContext文件放入其中。

現在,讓我們繼續前進。

創建您的上下文 API

我們將在ThemeContext.js文件中創建主題上下文。

要創建上下文,我們使用React.createContext創建上下文對象。 你可以將任何東西作為參數傳遞給React.createContext 。 在這種情況下,我們將傳入一個作為當前主題模式的字符串。 所以現在我們當前的主題模式是“輕”主題模式。

 import React from "react"; const ThemeContext = React.createContext("light"); export default ThemeContext;

為了讓我們所有的 React 組件都可以使用這個上下文,我們必須使用 Provider。 什麼是提供者? 根據 React 文檔,每個上下文對像都帶有一個 Provider React 組件,允許消費組件訂閱上下文更改。 它是允許其他組件使用上下文的提供者。 也就是說,讓我們創建我們的提供者。

轉到您的App.js文件。 為了創建我們的提供者,我們必須導入我們的ThemeContext

導入ThemeContext ,我們必須將App組件的內容包含在ThemeContext.Provider標記中,並為ThemeContext.Provider組件提供一個名為value的道具,該道具將包含我們想要提供給組件樹的數據。

 function App() { const theme = "light"; return ( <ThemeContext.Provider value = {theme}> <div> </div> </ThemeContext.Provider> ); }

所以現在我們所有的組件都可以使用“light”的值(我們很快就會寫出來)。

創建我們的主題文件

現在,我們將創建包含淺色和深色主題的不同顏色值的主題文件。 在您的/src文件夾中創建一個名為Colors.js的文件。

Colors.js中,我們將創建一個名為AppTheme的對象。 該對象將包含我們主題的顏色。 完成後,像這樣導出AppTheme對象:

 const AppTheme = { light: { textColor: "#000", backgroundColor: "#fff" }, dark: { textColor: "#fff", backgroundColor: "#333" } } export default AppTheme;

現在是時候開始創建我們不同的 React 組件了。

創建我們的 React 組件

讓我們創建以下組件:

  • Header
  • ThemeToggler
  • MainWithClass

頁眉.jsx

 import React from "react"; import ThemeToggler from "./ThemeToggler"; const headerStyles = { padding: "1rem", display: "flex", justifyContent: "space-between", alignItems: "center" } const Header = () => { return( <header style = {headerStyles}> <h1>Context API</h1> <ThemeToggler /> </header> ); } export default Header;

ThemeToggler.jsx

(現在,我們只返回一個空的div 。)

 import React from "react"; import ThemeContext from "../Context/ThemeContext"; const themeTogglerStyle = { cursor: "pointer" } const ThemeToggler = () => { return( <div style = {themeTogglerStyle}> </div> ); } export default ThemeToggler;

使用基於類的組件使用上下文

在這裡,我們將使用ThemeContext的值。 你可能已經知道,我們有兩種在 React 中編寫組件的方法:通過函數或類。 兩種方法中使用上下文的過程是不同的,所以我們將創建兩個組件作為我們應用程序的主要部分: MainWithClassMainWithFunction

讓我們從MainWithClass開始。

MainWithClass.jsx

我們將不得不導入我們的ThemeContextAppTheme 。 完成後,我們將編寫一個從渲染方法返回 JSX 的類。 現在我們必須使用我們的上下文。 對於基於類的組件,有兩種方法可以做到這一點:

  1. 第一種方法是通過Class.contextType

    要使用此方法,我們將ThemeContext中的上下文對象分配給類的contextType屬性。 之後,我們將能夠使用this.context訪問上下文值。 您還可以在任何生命週期方法甚至渲染方法中引用它。

     import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component{ constructor(){ super(); } static contextType = ThemeContext; render(){ const currentTheme = AppTheme[this.context]; return( <main></main> ); } }

    ThemeContext分配給我們類的contextType屬性後,我將當前主題對象保存在currentTheme變量中。

    現在,我們將從currentTheme變量中獲取顏色並使用它們來設置一些標記的樣式。
     render() { const currentTheme = AppTheme[this.context]; return ( <main style={{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main>

    而已! 但是,此方法將您限制為僅使用一個上下文。
  2. 第二種方法是ThemeContext.Consumer ,它涉及到消費者的使用。 每個上下文對像還帶有一個 Consumer React 組件,可以在基於類的組件中使用。 消費者組件將一個子組件作為一個函數,並且該函數返回一個 React 節點。 當前上下文值作為參數傳遞給該函數。

    現在,讓我們將MainWithClass組件中的代碼替換為:
     class Main extends Component { constructor() { super(); this.state = { } } render(){ return( <ThemeContext.Consumer> { (theme) => { const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ) } } </ThemeContext.Consumer> ); } }

    如您所見,我們使用了ThemeContext的當前值,我們將其命名為“主題”,並且我們獲取了該主題模式的顏色值並將其分配給變量currentTheme 。 使用這種方法,您可以使用多個消費者。

這是使用基於類的組件使用上下文的兩種方法。

使用功能組件使用上下文

使用功能組件使用上下文比使用基於類的組件更容易且不那麼乏味。 要在功能組件中使用上下文,我們將使用一個名為useContext的鉤子。

下面是使用帶有功能組件的ThemeContext的樣子:

 const Main = () => { const theme = useContext(ThemeContext); const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } export default Main;

如您所見,我們所要做的就是使用我們的useContext鉤子, ThemeContext作為參數傳入。

注意您必須在 App.js 文件中使用這些不同的組件才能看到結果。

使用ThemeToggler組件更新我們的主題

現在我們將處理我們的ThemeToggler組件。 我們需要能夠在明暗主題之間切換。 為此,我們需要編輯ThemeContext.js 。 我們的React.createContext現在將接受一個類似於useState鉤子結果的對像作為參數。

 const ThemeContext = React.createContext(["light", () => {}]);

我們將一個數組傳遞給React.createContext函數。 數組中的第一個元素是當前主題模式,第二個元素是用於更新主題的函數。 正如我所說,這只是類似於useState鉤子的結果,但它不完全是useState鉤子的結果。

現在我們將編輯我們的App.js文件。 我們需要將傳遞給提供者的值更改為useState掛鉤。 現在我們的 Theme Context 的值是一個useState鉤子,它的默認值是“light”。

 function App() { const themeHook = useState("light"); return ( <ThemeContext.Provider value = {themeHook}> <div> <Header /> <Main /> </div> </ThemeContext.Provider> ); }

編寫我們的ThemeToggler組件

現在讓我們實際編寫ThemeToggler組件:

 import React,{useContext} from "react"; import ThemeContext from "../Context/ThemeContext"; const themeTogglerStyle = { cursor: "pointer" } const ThemeToggler = () => { const[themeMode, setThemeMode] = useContext(ThemeContext); return( <div style = {themeTogglerStyle} onClick = {() => {setThemeMode(themeMode === "light"? "dark": "light")}}> <span title = "switch theme"> {themeMode === "light" ? "" : "️"} </span> </div> ); } export default ThemeToggler;

由於我們的主題上下文的值現在是一個鉤子,每當我們在其上調用useContext時,它將返回一個數組。 使用解構,我們能夠從數組中獲取元素。 然後,我們為ThemeToggler編寫了一個onClick事件處理程序。 使用該代碼,每當單擊主題切換器時,它都會切換我們應用程序的主題。

現在我們將編輯Main組件的不同版本。

編輯我們的MainWithClass組件

  1. 使用Class.contextType方法的MainWithClass組件的版本:
     import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component{ constructor(){ super(); } static contextType = ThemeContext; render(){ const currentTheme = AppTheme[this.context[0]]; return( <main style={{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } }
  2. 使用ThemeContext.Consumer方法的MainWithClass組件的版本:
     import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component { constructor() { super(); this.state = {} } render() { return ( <ThemeContext.Consumer> { ([theme]) => { const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ) } } </ThemeContext.Consumer> ); } } export default Main;

編輯我們的MainWithFunction組件

MainWithFunction組件應編輯如下:

 import React, { useContext } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; const Main = () => { const theme = useContext(ThemeContext)[0]; const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } export default Main;

結論

而已! 我們已經使用 Context API 成功地為我們的 React 應用程序實現了兩種主題模式。

在這個過程中,我們了解到:

  • Context API 是什麼以及它解決的問題;
  • 何時使用 Context API;
  • 創建Context並在功能和基於類的組件中使用它。

關於 SmashingMag 的進一步閱讀

  • 現代 Web 應用程序中的樣式
  • 使用 Ionic 和 React 構建移動應用程序
  • 使用 Webpack 和 Workbox 構建 PWA
  • 了解 MutationObserver API