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