2022 年你需要知道的关于 React Context 的一切,第 1 部分

文章摘录

2022 年你需要知道的关于 React Context 的一切,第 1 部分

摘录自 快速反应,第二版 作者:莫滕·巴克伦德

这段摘录探讨了使用 React Context。

如果您是 React 开发人员或想了解有关 React 的更多信息,请阅读它。

享受 25% 的折扣 快速反应,第二版 通过输入 fccmardan2 在结帐时进入折扣代码框 曼宁网 .

让我们开始构建一个解决实际问题的应用程序。我们将制作一个用户仪表板,即您登录应用程序后看到的屏幕。此仪表板有一条欢迎消息,按名称欢迎您,左上角还有一个按钮,显示您的姓名和指向您的设置页面的链接。这里的诀窍是名称是动态的,并将由后端返回给我们。

最终结果应该如图 1 所示。

Figure 1 The desired end result for our user dashboard.. The user’s name is displayed twice in this screenshot, which is the core of the issue here.

让我们将其分解为组件。我们希望顶部菜单成为标题的一部分,而中央欢迎页面只是我们的应用程序可以显示的众多页面之一。我们知道我们将来会添加更多的东西,所以让我们添加一些额外的层来准备。我们将使用图 2 所示的组件方法。

Figure 2 Our component structure with the necessary placeholders needed for the name. Note how the “name” property is used twice, but still not passed along as a property anywhere.

但是,正如您在图 2 中看到的,我们没有显示如何从组件树的最顶部获取名称,它被传递到 Dashboard 组件一直到最后需要的两个较小的组件显示它。

仅使用常规的 React 属性,我们需要将属性通过每个组件传递给需要它的组件。如果我们这样做,它看起来像图 3。

Figure 3 Our component structure if we pass the name to every component that needs to pass it on. There’s a total of five components needing the name property but only two of them actually display it

但请注意,在此组件树中,我们将 name 属性传递给 Header 和 Main 组件。这些组件本身都不需要这个属性。我们必须将此属性传递给这两个组件的唯一原因是它们可以将属性转发给另一个组件。

尽管如此,这当然是可行的,我们可以实现它,所以让我们在清单 1 中实现它。

清单 1 带有大量名称道具的仪表板。

 常量 BUTTON_STYLE = {  
 显示:'内联块',  
 填充:'4px 10px',  
 背景:'透明',  
 边框:'0',  
 };  
 常量 HEADER_STYLE = {  
 显示:'弹性',  
 justifyContent: 'flex-end',  
 borderBottom: '1px 实心',  
 };  
 功能按钮({孩子}){  
 返回<button style={BUTTON_STYLE}>{孩子}</button> ;  
 }  
 功能用户按钮({名称}){  
 返回 {名称} ; #一个  
 }  
 函数标题({名称}){#B  
 返回 (  
 <header style={HEADER_STYLE}>  
 <Button>家</button>  
 <Button>团体</button>  
 <Button>轮廓</button>  
 <UserButton name={name} />#C  
 </header>  
 );  
 }  
 功能欢迎({名称}){  
 返回<section><h1>欢迎您,{name}!</h1></section> ;  
 }  
 函数主({名称}){#B  
 返回<main><Welcome name={name} /></main>; #C  
 }  
 功能仪表板({名称}){  
 返回 <><Header name={name} /><Main name={name} /></> ;  
 }

#A 你知道你可以直接在 React 中使用表情符号吗?你可以!

#B 这里我们将 name 属性传递给实际上不需要使用该属性本身的组件

#C 组件仅通过属性才能将其传递给另一个组件。

源代码

您可以从以下位置下载上述示例的源代码 https://github.com/rq2e/rq2e/tree/main/ch10/rq10-dashboard-props 或者您可以通过运行以下代码段来使用此示例预填充来初始化一个新的 React 项目:

 npx create-react-app rq10-dashboard-props --template rq10-dashboard-props

这是一种合理的方法,并且有效。如果您在浏览器中打开它,您会在图 1 中看到我们想要的内容。

反应上下文

这些属性被传递给组件,只是为了将它们传递给另一个组件,这看起来不像是好的软件设计。一定会有更好的办法。如果我们可以有一个封装了许多组件的存储对象,当他们请求数据时,它可以将数据提供给它的所有子组件?他们应该能够这样做,而无需传递任何额外的属性。

恭喜,我们刚刚发明了 React Context。上下文正是这样做的。它用一个值包装了许多组件,所有后代组件都可以访问而无需通过属性。

支柱钻孔

将属性添加到组件的唯一目的是允许该组件将这些属性传递给其他组件,而这又可能只需要允许这些组件将这些属性传递给另一层组件的做法称为 支柱钻孔 .你 钻头 您的 财产 通过很多层的组件,因为你需要从外到内得到它。

在大型代码库中,道具钻探很快就会成为一个问题,而 React Context 是解决这个问题的最佳工具之一。如果没有适当的设计模式(例如使用上下文提供程序),您最终可能会在某些组件上拥有数十个属性,这些属性只是因为在组件树的下方需要它们而添加。

这显然是糟糕的软件设计,也是 React Context 如此受欢迎的原因之一。

React 中的上下文由两部分组成。它需要一个提供者,其中包含您要传递给任何后代组件的值,并且它需要一个使用者,您可以在每个想要访问所提供值的后代组件中使用它。

上下文提供者是一个非常简单的 React 组件。消费者可以通过两种不同的方式创建:作为具有子功能的组件或作为 使用上下文 钩。前一种方法非常不寻常且很麻烦,所以我们根本不打算使用它。在现代 React 应用程序中,你可能永远不会看到它被使用。它仅在基于类的代码库中有用,您不能在其中使用钩子变体。

本质上,使用上下文类似于图 4。

Figure 4 Using a hook with a provider and a consumer using the useContext hook.

我们在这里需要两个 React API。首先,我们需要 createContext 来定义上下文,我们将其存储在一个变量中。这是一个在任何组件之外创建的变量,并且与其他组件位于相同的位置,因此可以像任何其他组件一样被引用。

另一部分是 useContext 钩子。这个钩子引用上下文并返回当前上下文值。

让我们继续往我们的仪表板应用程序中添加一个 NameContext 从之前的组件树中。我们在图 5 中这样做。

Figure 5 The dashboard application component tree with a context surrounding it all.

这就是它所需要的一切。我们可以继续在清单 2 中实现它。

清单 2 带有上下文的仪表板。

 导入 { createContext, useContext } from 'react'; #一个  
 常量 BUTTON_STYLE = {  
 显示:'内联块',  
 填充:'4px 10px',  
 背景:'透明',  
 边框:'0',  
 };  
 常量 HEADER_STYLE = {  
 显示:'弹性',  
 justifyContent: 'flex-end',  
 borderBottom: '1px 实心',  
 };  
 常量 NameContext = createContext(); #B  
 功能按钮({孩子}){  
 返回<button style={BUTTON_STYLE}>{孩子}</button> ;  
 }  
 功能用户按钮(){#C  
 常量名称 = useContext(NameContext); #D  
 返回 {名称} ;  
 }  
 函数标题(){#C  
 返回 (  
 <header style={HEADER_STYLE}>  
 <Button>家</button>  
 <Button>团体</button>  
 <Button>轮廓</button>  
 <UserButton />  
 </header>  
 );  
 }  
 函数欢迎(){#C  
 常量名称 = useContext(NameContext); #D  
 返回<section><h1>欢迎您,{name}!</h1></section> ;  
 }  
 函数 Main() { #C  
 返回<main><Welcome /></main>;  
 }  
 功能仪表板({名称}){  
 返回 (  
 <NameContext.Provider value={name}>#E  
 <Header />  
 <Main />  
 </NameContext.Provider>  
 );  
 }

#A 我们从 React 包中导入这两个函数

#B 上下文是在全局范围内创建的,因此我们可以从任何地方访问它。

#CA 我们的许多组件不再具有任何属性。

#D 需要访问名称的两个组件可以通过使用 useContext 连接到上下文来实现。

#E 在仪表板组件中,我们确保将整个树包装在上下文提供程序中,并将名称作为上下文值。

源代码

您可以从以下位置下载上述示例的源代码 https://github.com/rq2e/rq2e/tree/main/ch10/rq10-dashboard-context 或者您可以通过运行以下代码段来使用此示例预填充来初始化一个新的 React 项目:

 npx create-react-app rq10-dashboard-context --template rq10-dashboard-context

我们得到与以前完全相同的结果,但我们认为数据流要好得多。

上下文挂钩是有状态的

使用上下文来存储在整个应用程序中使用的静态值肯定很好,但更好的是我们也可以在那里存储动态信息。这 使用上下文 钩子是有状态的,所以如果上下文值改变, 使用上下文 hook 将导致使用它的组件自动重新渲染。

让我们想象同样的仪表板,但这次您是一名管理员,希望能够查看数据库中任何用户的仪表板外观。作为管理员,您有一个可以查看仪表板的用户下拉列表。我们将像图 6 那样实现它,其中 Dashboard 组件与之前的组件相同(我们只是不会显示其所有子组件以节省空间)。

Figure 6 The admin dashboard allows the user to choose which user to see the dashboard for. The admin dashboard includes a select box and the regular user dashboard.

我们将使用一个简单的选择元素来允许用户在系统中的三个用户之间进行选择:Alice、Bob 和 Carol。我们可以使用一个简单的 useState 来记住选定的用户,并根据需要将其传递给组件。让我们使用清单 3 中的这个新管理员仪表板扩展前面的示例。

清单 3 管理员仪表板。

 从'react'导入{useState,createContext,useContext}; #一个  
 常量 BUTTON_STYLE = {  
 显示:'内联块',  
 填充:'4px 10px',  
 背景:'透明',  
 边框:'0',  
 };  
 常量 HEADER_STYLE = {  
 显示:'弹性',  
 justifyContent: 'flex-end',  
 borderBottom: '1px 实心',  
 };  
 常量 NameContext = createContext();  
 功能按钮({孩子}){  
 返回<button style={BUTTON_STYLE}>{孩子}</button> ;  
 }  
 功能用户按钮(){  
 常量名称 = useContext(NameContext);  
 返回 {名称} ;  
 }  
 函数头(){  
 返回 (  
 <header style={HEADER_STYLE}>  
 <Button>家</button>  
 <Button>团体</button>  
 <Button>轮廓</button>  
 <UserButton />  
 </header>  
 );  
 }  
 功能欢迎(){  
 常量名称 = useContext(NameContext);  
 返回<section><h1>欢迎您,{name}!</h1></section> ;  
 }  
 功能主要(){  
 返回<main><Welcome /></main>;  
 }  
 功能仪表板({名称}){#B  
 返回 (  
 <NameContext.Provider value={name}>  
 <Header />  
 <Main />  
 </NameContext.Provider>  
 );  
 }  
 功能管理仪表板(){  
 const [user, setUser] = useState('Alice'); #C  
 返回 (  
 <>  
 <select value={user} onChange={(evt) =>设置用户(evt.target.value)}> #D  
 <option value=Alice>爱丽丝</option>  
 <option value=Bob>鲍勃</option>  
 <option value=Carol>颂歌</option>  
 </select>  
 <Dashboard name={user} />#E  
 </>  
 );  
 }

#A 我们还需要导入 useState 钩子

#B Dashboard 组件内的一切都和以前一样

#C 我们创建一个简单的状态,默认为 Alice

#D 我们使用受控选择元素来选择用户

#E 我们将当前选择的用户传递给仪表板组件。

源代码

您可以从以下位置下载上述示例的源代码 https://github.com/rq2e/rq2e/tree/main/ch10/rq10-dashboard-admin 或者您可以通过运行以下代码段来使用此示例预填充来初始化一个新的 React 项目:

 npx create-react-app rq10-dashboard-admin --template rq10-dashboard-admin

如果我们在浏览器中尝试此操作,如图 7 所示。继续并从下拉列表中选择一个不同的名称,然后在菜单和标题的仪表板中看到名称正确更新。

Figure 7 The admin dashboard displaying the user dashboard for Carol, as we have selected her name in the admin dropdown in the top left.

记忆

不过,这里还有一件事要做。您可能会想,当我们在名称上下文中更改名称时,我们如何确保正确的组件正在重新渲染?这是一个公平的问题。因为如果您在 React 开发人员工具中打开调试选项,该选项会突出显示任何重新渲染的组件,您会看到所有组件都在重新渲染,如图 8 所示。

Figure 8 All the components re-render when the user selection changes. You can see that the entire header re-renders, because all the buttons in the header individually re-render — they all have a blue box around them.

原因很简单。任何组件在其父组件重新渲染时都会重新渲染。所以当 Dashboard 组件重新渲染时,它的两个子组件 Header 和 Main 也会重新渲染。当他们渲染时,他们所有的孩子也会这样做,等等。但是这两个组件,Main 和 Header,实际上不需要重新渲染,因为它们没有任何改变。我们可以通过记忆这两个组件来通知 React。让我们继续在清单 4 中执行此操作。

清单 4 带有记忆功能的管理员仪表板。

 从'react'导入{备忘录,useState,createContext,useContext}; #一个  
 常量 BUTTON_STYLE = {  
 显示:'内联块',  
 填充:'4px 10px',  
 背景:'透明',  
 边框:'0',  
 };  
 常量 HEADER_STYLE = {  
 显示:'弹性',  
 justifyContent: 'flex-end',  
 borderBottom: '1px 实心',  
 };  
 常量 NameContext = createContext();  
 功能按钮({孩子}){  
 返回<button style={BUTTON_STYLE}>{孩子}</button> ;  
 }  
 功能用户按钮(){  
 常量名称 = useContext(NameContext);  
 返回 {名称} ;  
 }  
 常量头=备忘录(函数头(){#B  
 返回 (  
 <header style={HEADER_STYLE}>  
 <Button>家</button>  
 <Button>团体</button>  
 <Button>轮廓</button>  
 <UserButton />  
 </header>  
 );  
 }  
 功能欢迎(){  
 常量名称 = useContext(NameContext);  
 返回<section><h1>欢迎您,{name}!</h1></section> ;  
 }  
 const Main = memo(function Main() { #B  
 返回<main><Welcome /></main>;  
 }  
 功能仪表板({名称}){  
 返回 (  
 <NameContext.Provider value={name}>  
 <Header />  
 <Main />  
 </NameContext.Provider>  
 );  
 }  
 功能管理仪表板(){  
 const [user, setUser] = useState('Alice');  
 返回 (  
 <>  
 <select value={user} onChange={(evt) =>设置用户(evt.target.value)}>  
 <option value=Alice>爱丽丝</option>  
 <option value=Bob>鲍勃</option>  
 <option value=Carol>颂歌</option>  
 </select>  
 <Dashboard name={user} />  
 </>  
 );  
 }

#A 我们只改变两件事。我们从 React 包中导入备忘录功能。

#B 我们用它来记忆两个组件。

源代码

您可以从以下位置下载上述示例的源代码 https://github.com/rq2e/rq2e/tree/main/ch10/rq10-dashboard-memo 或者您可以通过运行以下代码段来使用此示例预填充来初始化一个新的 React 项目:

 npx create-react-app rq10-dashboard-memo --template rq10-dashboard-memo

当我们现在在浏览器中打开它并在启用调试器的情况下更改用户时,我们看到只有所需的组件重新呈现,如图 9 所示。

Figure 9 This time, only the correct elements are re-rendering. It looks like there’s a blue box around the header, but it’s actually around the entire Dashboard component (which is supposed to re-render). You can see that the header component is not re-rendering, because the other buttons in the header are clearly not re-rendering.

使用上下文 钩子正在工作。它们会导致其组件重新渲染,而其父组件根本不会重新渲染——就像有状态的钩子应该做的那样!

这很漂亮。如果我们思考其含义,整个上下文概念是非常强大的。我们稍后会做,但首先我们将更详细地了解上下文 API。

查看第 2 部分了解更多信息。谢谢阅读。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明

本文链接:https://www.qanswer.top/39050/50342411

posted @ 2022-09-24 11:51  哈哈哈来了啊啊啊  阅读(41)  评论(0编辑  收藏  举报