作者选择了 Creative Commons以作为 Write for Donations计划的一部分获得捐赠。
介绍
在本教程中,您将使用 React context共享多个组件的状态。React context 是与其他 components共享信息的界面,而不明确将数据传输为 props。
React 环境足够灵活,可以作为项目的集中状态管理系统使用,或者您可以将其扩展到应用程序的较小的部分。 通过环境,您可以在应用程序中共享数据,而不需要任何额外的第三方工具,并且配置量很小。
在本教程中,您将使用语境来构建一个应用程序,该应用程序使用不同组件的常见数据集。为了说明这一点,您将创建一个网站,用户可以构建自定义沙拉。网站将使用语境来存储客户信息,喜爱的项目和自定义沙拉。
前提条件
- 联合国 您需要运行一个开发环境 [Node.js] (https://nodejs.org/en/about/); 此教程在Node.js 版本10.20.1和npm 版本6.14.4. 上进行了测试. 要在 macOS 或 Ubuntu 18.04 上安装此功能,请遵循 [如何在 macOS (https://andsky.com/tech/tutorials/how-to-install-node-js-and-create-a-local-development-environment-on-macos 上安装节点并创建本地开发环境] 或 [如何在 Ubuntu 18.04 (https://andsky.com/tech/tutorials/how-to-install-node-js-on-ubuntu-18-04 上安装节点.js] 的 ** 部分使用 PPA** 。
- 用Create React App来设置反应开发环境,将非必需锅炉板取出. 要设置此功能, 请遵循 [步骤1—— 创建一个空项目, 如何管理响应类组件的状态教程] (https://andsky.com/tech/tutorials/how-to-manage-state-on-react-class-components#step-1-%E2%80%94-creating-an-empty-project) 。 此教程将使用
state- context-tutorial
作为项目名称。 - 您还需要 JavaScript 的基本知识, 您可以在 [How To Code in JavaScript] (https://www.digitalocean.com/community/tutorial_series/how-to-code-in-javascript] 中找到, 同时您还需要 HTML 和 CSS 的基本知识 。 HTML和CSS有用的资源是Mozilla开发者网络.
- 您将使用 React 组件,
使用状态
钩和使用减少器
Hook,您可以在我们的教程中学习到[如何在反应中创建自定义组件 (https://andsky.com/tech/tutorials/how-to-create-custom-components-in-react) 和[如何在反应组件上用钩子管理状态 (https://andsky.com/tech/tutorials/how-to-manage-state-with-hooks-on-react-components). (英语)
步骤1 - 构建您的应用程序的基础
在此步骤中,您将构建您自定义沙拉构造器的总体结构。您将创建组件以显示可能的顶点、选定的顶点列表和客户信息. 当您使用静态数据构建应用程序时,您将发现如何在各种组件中使用不同的信息片段,以及如何识别在一个背景下有用的数据片段。
以下是您将构建的应用程序的例子:
注意您可能需要在各组件中使用的信息如何。例如,用户名(此样本为 Kwame)在导航区域中显示用户数据,但您可能还需要用户信息来识别喜爱的项目或用于支票页面。
然后,您将从下一步开始在文本中添加内容. 文本在应用开始增长时提供了最大的价值,因此,在这一步中,您将构建多个组件,以显示文本如何在组件树上工作。
由于您正在构建一个具有多个组件的小型应用程序,请安装 JSS,以确保不会出现类别名称冲突,并以便您可以作为组件在同一文件中添加样式。
运行以下命令:
1npm install react-jss
npm 将安装该组件,当它完成时,您将看到这样的消息:
1[secondary_label Output]
2+ [email protected]
3added 27 packages from 10 contributors, removed 10 packages andaudited 1973 packages in 15.507s
现在你已经安装了 JSS,考虑一下你需要的不同组件。 页面顶部,你将有一个导航
组件来存储欢迎消息。 下一个组件将是SaladMaker
本身。 这将包含名称以及构建者和 Your Salad列表在底部。 成分的部分将是一个名为SaladBuilder
的单独组件,嵌入SaladMaker
内部。 每个组件将是一个SaladItem
组件的实例。 最后,底部列表将是一个名为SaladSummary
的组件。
<$>[注] 注: 组件不需要以这种方式分割。当你在应用程序上工作时,你的结构会随着你添加更多的功能而发生变化和演变。
现在你已经有了你需要的组件的想法,为每个组件创建一个目录:
1mkdir src/components/Navigation
2mkdir src/components/SaladMaker
3mkdir src/components/SaladItem
4mkdir src/components/SaladBuilder
5mkdir src/components/SaladSummary
接下来,从上到下构建组件,从导航
开始,首先在文本编辑器中打开组件文件:
1nano src/components/Navigation/Navigation.js
创建一个名为导航
的组件,并添加一些风格来给导航
一个边界和垫:
1[label state-context-tutorial/src/components/Navigation/Navigation.js]
2import React from 'react';
3import { createUseStyles } from 'react-jss';
4
5const useStyles = createUseStyles({
6 wrapper: {
7 borderBottom: 'black solid 1px',
8 padding: [15, 10],
9 textAlign: 'right',
10 }
11});
12
13export default function Navigation() {
14 const classes = useStyles();
15 return(
16 <div className={classes.wrapper}>
17 Welcome, Kwame
18 </div>
19 )
20}
由于您正在使用 JSS,您可以直接在组件中创建风格对象,而不是 CSS 文件. 包装器div
将有一个插头,一个固体``黑色
边界,并将文本与textAlign
对齐。
然后打开App.js
,这是项目的根源:
1nano src/components/App/App.js
导入导航
组件并通过添加突出的行将其放入空标签中:
1[label state-context-tutorial/src/components/App/App.js]
2import React from 'react';
3import Navigation from '../Navigation/Navigation';
4
5function App() {
6 return (
7 <>
8 <Navigation />
9 </>
10 );
11}
12
13export default App;
保存和关闭文件. 当你这样做时,浏览器将更新,你会看到导航栏:
将导航栏视为一个 _global 组件,因为在本示例中,它作为一个模板组件,将在每个页面上重复使用。
下一个组件将是SaladMaker
本身,这是一个只能在某些页面或某些状态下进行的组件。
在文本编辑器中打开SaladMaker.js
:
1nano src/components/SaladMaker/SaladMaker.js
创建一个具有标签<h1>
的组件:
1[label state-context-tutorial/src/components/SaladMaker/SaladMaker.js]
2import React from 'react';
3import { createUseStyles } from 'react-jss';
4
5const useStyles = createUseStyles({
6 wrapper: {
7 textAlign: 'center',
8 }
9});
10
11export default function SaladMaker() {
12 const classes = useStyles();
13 return(
14 <>
15 <h1 className={classes.wrapper}>
16 <span role="img" aria-label="salad">🥗 </span>
17 Build Your Custom Salad!
18 <span role="img" aria-label="salad"> 🥗</span>
19 </h1>
20 </>
21 )
22}
在此代码中,您正在使用textAlign
来将组件集中在页面上. 该span
元素的角色
和aria-label
属性将帮助使用 Accessible Rich Internet Applications (ARIA)实现可访问性。
保存并关闭文件. 打开App.js
以返回组件:
1nano src/components/App/App.js
导入SaladMaker
并在导航
组件后渲染:
1[label state-context-tutorial/src/components/App/App.js]
2import React from 'react';
3import Navigation from '../Navigation/Navigation';
4import SaladMaker from '../SaladMaker/SaladMaker';
5
6function App() {
7 return (
8 <>
9 <Navigation />
10 <SaladMaker />
11 </>
12 );
13}
14
15export default App;
保存并关闭文件. 当您这样做时,页面将重新加载,您将看到标题:
接下来,创建一个名为SaladItem
的组件,这将是每个组件的卡片。
打开文本编辑器中的文件:
1nano src/components/SaladItem/SaladItem.js
此组件将有三个部分:项目的名称,一个标志表明该项目是否是用户的最爱,并放置在一个按钮中,将该项目添加到沙拉上点击。
1[label state-context-tutorial/src/components/SaladItem/SaladItem.js]
2import React from 'react';
3import PropTypes from 'prop-types';
4import { createUseStyles } from 'react-jss';
5
6const useStyles = createUseStyles({
7 add: {
8 background: 'none',
9 border: 'none',
10 cursor: 'pointer',
11 },
12 favorite: {
13 fontSize: 20,
14 position: 'absolute',
15 top: 10,
16 right: 10,
17 },
18 image: {
19 fontSize: 80
20 },
21 wrapper: {
22 border: 'lightgrey solid 1px',
23 margin: 20,
24 padding: 25,
25 position: 'relative',
26 textAlign: 'center',
27 textTransform: 'capitalize',
28 width: 200,
29 }
30});
31
32export default function SaladItem({ image, name }) {
33 const classes = useStyles();
34 const favorite = true;
35 return(
36 <div className={classes.wrapper}>
37 <h3>
38 {name}
39 </h3>
40 <span className={classes.favorite} aria-label={favorite ? 'Favorite' : 'Not Favorite'}>
41 {favorite ? '😋' : ''}
42 </span>
43 <button className={classes.add}>
44 <span className={classes.image} role="img" aria-label={name}>{image}</span>
45 </button>
46 </div>
47 )
48}
49
50SaladItem.propTypes = {
51 image: PropTypes.string.isRequired,
52 name: PropTypes.string.isRequired,
53}
代码使用喜爱
变量和(https://andsky.com/tech/tutorials/how-to-write-conditional-statements-in-javascript#ternary-operator)外部操作员(https://andsky.com/tech/tutorials/how-to-write-conditional-statements-in-javascript#ternary-operator)来条件定义喜爱
图标是否出现。喜爱
变量将随后以用户个人资料的一部分的背景来确定。现在,将其设置为真
。风格将将喜爱图标放在卡的右上角,并在按钮上删除默认边界和背景。包装
类将添加一个小边界,并转换部分文本。
保存并关闭文件. 现在,您需要渲染不同的项目. 您将使用一个名为SaladBuilder
的组件来完成此操作,该组件将包含一个将其转换为一系列的SaladItem
组件的项目列表:
打开SaladBuilder
:
1nano src/components/SaladBuilder/SaladBuilder.js
如果这是一个生产应用程序,这些数据通常来自应用程序编程接口(API)。
1[label state-context-tutorial/src/components/SaladBuilder/SaladBuilder.js]
2import React from 'react';
3import SaladItem from '../SaladItem/SaladItem';
4
5import { createUseStyles } from 'react-jss';
6
7const useStyles = createUseStyles({
8 wrapper: {
9 display: 'flex',
10 flexWrap: 'wrap',
11 padding: [10, 50],
12 justifyContent: 'center',
13 }
14});
15
16const ingredients = [
17 {
18 image: '🍎',
19 name: 'apple',
20 },
21 {
22 image: '🥑',
23 name: 'avocado',
24 },
25 {
26 image: '🥦',
27 name: 'broccoli',
28 },
29 {
30 image: '🥕',
31 name: 'carrot',
32 },
33 {
34 image: '🍷',
35 name: 'red wine dressing',
36 },
37 {
38 image: '🍚',
39 name: 'seasoned rice',
40 },
41];
42
43export default function SaladBuilder() {
44 const classes = useStyles();
45 return(
46 <div className={classes.wrapper}>
47 {
48 ingredients.map(ingredient => (
49 <SaladItem
50 key={ingredient.name}
51 image={ingredient.image}
52 name={ingredient.name}
53 />
54 ))
55 }
56 </div>
57 )
58}
此片段使用 [map()
数组方法](https://andsky.com/tech/tutorials/how-to-use-array-methods-in-javascript-iteration-methods#map()对列表中的每个项目进行地图,将名称
和图像
作为SaladItem
组件的附加符号。 请确保在您地图时向每个项目添加钥匙
(https://andsky.com/tech/tutorials/how-to-create-react-elements-with-jsx#step-4-%E2%80%94-mapping-over-data-to-create-elements)。 此组件的样式为 flexbox 布局添加了flex
的显示,包裹了组件,并将其中心化。
保存并关闭文件。
最后,在SaladMaker
中渲染组件,以便它出现在页面上。
打开SaladMaker
:
1nano src/components/SaladMaker/SaladMaker.js
然后输入SaladBuilder
,然后在标题后渲染:
1[label state-context-tutorial/src/components/SaladMaker/SaladMaker.js]
2import React from 'react';
3import { createUseStyles } from 'react-jss';
4import SaladBuilder from '../SaladBuilder/SaladBuilder';
5
6const useStyles = createUseStyles({
7 wrapper: {
8 textAlign: 'center',
9 }
10});
11
12export default function SaladMaker() {
13 const classes = useStyles();
14 return(
15 <>
16 <h1 className={classes.wrapper}>
17 <span role="img" aria-label="salad">🥗 </span>
18 Build Your Custom Salad!
19 <span role="img" aria-label="salad"> 🥗</span>
20 </h1>
21 <SaladBuilder />
22 </>
23 )
24}
保存和关闭文件. 当你这样做时,页面将重新加载,你会找到内容:
最后一步是添加正在进行的沙拉摘要. 此组件将显示用户选择的项目列表. 目前,您将硬编码这些项目。
在你的文本编辑器中打开SaladSummary
:
1nano src/components/SaladSummary/SaladSummary.js
组件将是一个标题和一个未分类的项目列表. 您将使用 flexbox 将其包装:
1[label state-context-tutorial/src/components/SaladSummary/SaladSummary.jss]
2import React from 'react';
3import { createUseStyles } from 'react-jss';
4
5const useStyles = createUseStyles({
6 list: {
7 display: 'flex',
8 flexDirection: 'column',
9 flexWrap: 'wrap',
10 maxHeight: 50,
11 '& li': {
12 width: 100
13 }
14 },
15 wrapper: {
16 borderTop: 'black solid 1px',
17 display: 'flex',
18 padding: 25,
19 }
20});
21
22export default function SaladSummary() {
23 const classes = useStyles();
24 return(
25 <div className={classes.wrapper}>
26 <h2>Your Salad</h2>
27 <ul className={classes.list}>
28 <li>Apple</li>
29 <li>Avocado</li>
30 <li>Carrots</li>
31 </ul>
32 </div>
33 )
34}
保存檔案. 然後開啟「SaladMaker」來顯示項目:
1nano src/components/SaladMaker/SaladMaker.js
导入并在SaladBuilder
后添加SaladSummary
:
1[label state-context-tutorial/src/components/SaladMaker/SaladMaker.js]
2import React from 'react';
3import { createUseStyles } from 'react-jss';
4import SaladBuilder from '../SaladBuilder/SaladBuilder';
5import SaladSummary from '../SaladSummary/SaladSummary';
6
7const useStyles = createUseStyles({
8 wrapper: {
9 textAlign: 'center',
10 }
11});
12
13export default function SaladMaker() {
14 const classes = useStyles();
15 return(
16 <>
17 <h1 className={classes.wrapper}>
18 <span role="img" aria-label="salad">🥗 </span>
19 Build Your Custom Salad!
20 <span role="img" aria-label="salad"> 🥗</span>
21 </h1>
22 <SaladBuilder />
23 <SaladSummary />
24 </>
25 )
26}
保存和关闭文件. 当你这样做时,页面将更新,你会找到完整的应用程序:
导航
组件和SaladItem
组件都需要知道用户的某些东西:他们的名字和他们最喜欢的列表。 SaladItem
还需要更新在SaladSummary
组件中可访问的数据。
您可以将数据声明为一个共同的母体,然后在稍后访问,而无需明确地将其传递到组件等级。
在此步骤中,您创建了一个应用程序,允许用户从选项列表中构建沙拉。您创建了一组组件,需要访问或更新由其他组件控制的数据。
步骤 2 – 从根组件中提供数据
在此步骤中,您将使用背景来存储客户信息在组件的根部。您将创建一个自定义的背景,然后使用一个名为 Provider
的特殊包装组件,该组件将存储信息在项目的根部。
最基本的背景是共享信息的界面. 许多应用程序都有他们需要在应用程序中共享的一些通用信息,例如用户偏好、主题信息和网站范围内的应用程序更改。
若要添加背景,请创建一个名为用户
的新目录:
1mkdir src/components/User
用户
将不会是一个传统的组件,因为你将把它作为一个组件和作为一个特殊的胡克的数据,称为useContext
。
接下来,在文本编辑器中打开User.js
:
1nano src/components/User/User.js
在文件中,从 React 导入createContext
函数,然后执行函数并导出结果:
1[label state-context-tutorial/src/components/User/User.js]
2import { createContext } from 'react';
3
4const UserContext = createContext();
5export default UserContext;
通过执行该函数,您已注册了文本. 结果, UserContext
,是您将在组件中使用的内容。
保存并关闭文件。
下一步是将文本应用于一组元素。要做到这一点,你将使用一个名为提供商
的组件。提供商
是一个组件,它设置了数据,然后包裹了一些儿童组件。
由于用户数据将在整个项目中保持不变,请将其放置尽可能高的组件树上,在此应用程序中,您将将其放置在应用程序
组件的根层:
打开app
:
1nano src/components/App/App.js
添加以下突出的代码行以导入背景并通过数据:
1[label state-context-tutorial/src/components/App/App.js]
2import React from 'react';
3import Navigation from '../Navigation/Navigation';
4import SaladMaker from '../SaladMaker/SaladMaker';
5import UserContext from '../User/User';
6
7const user = {
8 name: 'Kwame',
9 favorites: [
10 'avocado',
11 'carrot'
12 ]
13}
14
15function App() {
16 return (
17 <UserContext.Provider value={user}>
18 <Navigation />
19 <SaladMaker />
20 </UserContext.Provider>
21 );
22}
23
24export default App;
在典型的应用程序中,你会收集用户数据或将其存储在服务器端渲染过程中,在这种情况下,你硬编码了一些数据,你可能会从API中获取。
接下来,您导入了UserContext
,然后将Navigation
和SaladMaker
包装到一个名为UserContext.Provider
的组件中。请注意,在这种情况下,UserContext
是作为标准的React组件的。
保存并关闭文件. 现在数据可在整个应用程序中使用。 但是,要使用数据,您需要再次导入并访问背景。
现在你已经设置了背景,你可以开始用动态值代替组件中的硬编码数据. 开始用你用UserContext.Provider
设置的用户数据代替导航
中的硬编码名称。
打开Navigation.js
:
1nano src/components/Navigation/Navigation.js
在导航
中,从组件目录中导入 React 中的useContext
和UserContext
。然后使用UserContext
作为参数调用useContext
。 与UserContext.Provider
不同,您不需要在 JSX中渲染UserContext
。 接口将返回您在值
prop 中提供的数据。 将数据保存到一个名为用户
的新变量,这是一个包含名称
和喜好
的对象。 您可以用用户名
替换硬代码的名称:
1[label state-context-tutorial/src/components/Navigation/Navigation.js]
2import React, { useContext } from 'react';
3import { createUseStyles } from 'react-jss';
4
5import UserContext from '../User/User';
6
7const useStyles = createUseStyles({
8 wrapper: {
9 outline: 'black solid 1px',
10 padding: [15, 10],
11 textAlign: 'right',
12 }
13});
14
15export default function Navigation() {
16 const user = useContext(UserContext);
17 const classes = useStyles();
18 return(
19 <div className={classes.wrapper}>
20 Welcome, {user.name}
21 </div>
22 )
23}
UserContext
在App.js
中作为组件工作,但在这里你更像是一个数据片而使用它,但是如果你愿意的话,它仍然可以作为一个组件。你可以通过使用消费者
作为用户背景
的一部分访问相同的数据。你通过在你的JSX中添加UserContext.Consumer
来获取数据,然后使用作为孩子访问数据的函数
(https://reactjs.org/docs/context.html#contextconsumer)。
虽然使用消费者
组件是可能的,但使用Hooks通常可以更短,更容易阅读,同时仍然提供相同的最新信息,这就是为什么本教程使用Hooks方法。
保存并关闭文件. 当你这样做时,页面将更新,你会看到相同的名称. 但这一次它已经动态更新:
在这种情况下,数据不会穿过许多组件,代表数据穿过的路径的组件树看起来会是这样的:
1| UserContext.Provider
2 | Navigation
您可以将这个用户名作为一个贴纸,在这个规模上,这可能是一个有效的策略。但是随着应用程序的增长,有可能导航
组件将移动。可能有一个名为头部
的组件,包裹了导航
组件和另一个组件,如标题栏
,或者你会创建一个模板
组件,然后在那里嵌入导航
。
下一个需要用户数据的组件是SaladItem
组件.在SaladItem
组件中,您将需要用户的喜好系列。
打开SaladItem.js
:
1nano src/components/SaladItem/SaladItem.js
导入useContext
和UserContext
,然后在UserContext
中拨打useContext
。
1[label state-context-tutorial/src/components/SaladItem/SaladItem.js]
2import React, { useContext } from 'react';
3import PropTypes from 'prop-types';
4import { createUseStyles } from 'react-jss';
5
6import UserContext from '../User/User';
7
8const useStyles = createUseStyles({
9...
10});
11
12export default function SaladItem({ image, name }) {
13 const classes = useStyles();
14 const user = useContext(UserContext);
15 const favorite = user.favorites.includes(name);
16 return(
17 <div className={classes.wrapper}>
18 <h3>
19 {name}
20 </h3>
21 <span className={classes.favorite} aria-label={favorite ? 'Favorite' : 'Not Favorite'}>
22 {favorite ? '😋' : ''}
23 </span>
24 <button className={classes.add}>
25 <span className={classes.image} role="img" aria-label={name}>{image}</span>
26 </button>
27 </div>
28 )
29}
30
31SaladItem.propTypes = {
32 image: PropTypes.string.isRequired,
33 name: PropTypes.string.isRequired,
34}
当你这样做时,浏览器会更新,你会看到只有最喜欢的项目有情绪:
与导航
不同,背景的旅行更远,组件树看起来像这样:
1| User.Provider
2 | SaladMaker
3 | SaladBuilder
4 | SaladItem
如果你不得不将数据作为支撑在树上传递,这将是很多工作,你可能会有未来的开发者重塑代码,忘记传递支撑。
在此步骤中,您创建了一个背景,并使用了提供商
来设置组件树中的数据。您还使用了useContext
链接访问了背景,并在多个组件中使用了背景。该数据是静态的,因此在初始设置后从未改变过,但会有一些时候您需要共享数据,并在多个组件中修改数据。
步骤 3 – 更新嵌入式组件的数据
在此步骤中,您将使用语境和useReducer
链接创建动态数据,嵌入组件可以消耗和更新。您将更新您的SaladItem
组件,以设置SaladSummary
将使用和显示的数据。
在此时,您的应用程序正在显示多个组件的用户数据,但没有任何用户交互。在上一个步骤中,您使用了语境来共享单个数据,但您也可以共享数据集,包括函数。
在你的应用程序中,每个SaladItem
都需要更新一个共享列表,然后你的SaladSummary
组件会显示用户选择的项目,并将其添加到列表中。
背景与其他状态管理解决方案(如Redux)之间的主要区别之一是,背景不打算成为一个中央存储库,您可以在应用程序中多次使用它,并在根层或组件树深处启动它,换句话说,您可以在整个应用程序中传播你的背景,创建专注的数据收集,而不必担心冲突。
若要保持背景集中,请创建提供者
,在可能的情况下将最接近的共享父母包裹起来,这意味着,而不是在应用程序
中添加另一个背景,您将在SaladMaker
组件中添加背景。
打开SaladMaker
:
1nano src/components/SaladMaker/SaladMaker.js
然后创建并导出一个名为SaladContext
的新背景:
1[label state-context-tutorial/src/components/SaladMaker/SaladMaker.js]
2import React, { createContext } from 'react';
3import { createUseStyles } from 'react-jss';
4import SaladBuilder from '../SaladBuilder/SaladBuilder';
5import SaladSummary from '../SaladSummary/SaladSummary';
6
7const useStyles = createUseStyles({
8 wrapper: {
9 textAlign: 'center',
10 }
11});
12
13export const SaladContext = createContext();
14
15export default function SaladMaker() {
16 const classes = useStyles();
17 return(
18 <>
19 <h1 className={classes.wrapper}>
20 <span role="img" aria-label="salad">🥗 </span>
21 Build Your Custom Salad!
22 <span role="img" aria-label="salad"> 🥗</span>
23 </h1>
24 <SaladBuilder />
25 <SaladSummary />
26 </>
27 )
28}
在之前的步骤中,您为您的背景创建了一个单独的组件,但在这种情况下,您正在创建它在您正在使用的相同文件中. 由于用户
似乎与应用
没有直接关系,所以保持它们分开可能更有意义。
此外,您还可以创建一个更通用的环境,称为OrderContext
,在多个组件中重复使用它。在这种情况下,您希望创建一个单独的组件。
在你添加提供者
之前,想想你想要共享的数据。你需要一系列的项目和一个添加项目的功能。与其他集中状态管理工具不同,环境不会处理你的数据的更新。它只会将数据保留在以后的使用中。 要更新数据,你需要使用其他状态管理工具,例如Hooks。 如果你正在为相同的组件收集数据,你会使用useState
或useReducer
Hooks。 如果你对这些Hooks是新手,请参阅如何管理React组件上的Hooks
。
该useReducer
钩子是一个很好的匹配,因为你需要在每个操作上更新最新的状态。
创建一个减少
函数,将新项目添加到一个状态
数组,然后使用useReducer
链接创建一个沙拉
数组和一个Salad
函数:
1[label state-context-tutorial/src/components/SaladMaker/SaladMaker.js]
2import React, { useReducer, createContext } from 'react';
3import { createUseStyles } from 'react-jss';
4import SaladBuilder from '../SaladBuilder/SaladBuilder';
5import SaladSummary from '../SaladSummary/SaladSummary';
6
7const useStyles = createUseStyles({
8 wrapper: {
9 textAlign: 'center',
10 }
11});
12
13export const SaladContext = createContext();
14
15function reducer(state, item) {
16 return [...state, item]
17}
18
19export default function SaladMaker() {
20 const classes = useStyles();
21 const [salad, setSalad] = useReducer(reducer, []);
22 return(
23 <>
24 <h1 className={classes.wrapper}>
25 <span role="img" aria-label="salad">🥗 </span>
26 Build Your Custom Salad!
27 <span role="img" aria-label="salad"> 🥗</span>
28 </h1>
29 <SaladBuilder />
30 <SaladSummary />
31 </>
32 )
33}
现在你有一个组件,包含你想要共享的沙拉
数据,一个名为setSalad
的函数来更新数据,以及SaladContext
来共享数据在同一个组件中。
要组合,你需要创建一个供应商
。问题在于供应商
将一个单一的值
作为支撑,因为你不能单独传输沙拉
和设置Salad
,你需要将它们组合成一个对象,并将对象传递为值
:
1[label state-context-tutorial/src/components/SaladMaker/SaladMaker.js]
2import React, { useReducer, createContext } from 'react';
3import { createUseStyles } from 'react-jss';
4import SaladBuilder from '../SaladBuilder/SaladBuilder';
5import SaladSummary from '../SaladSummary/SaladSummary';
6
7const useStyles = createUseStyles({
8 wrapper: {
9 textAlign: 'center',
10 }
11});
12
13export const SaladContext = createContext();
14
15function reducer(state, item) {
16 return [...state, item]
17}
18
19export default function SaladMaker() {
20 const classes = useStyles();
21 const [salad, setSalad] = useReducer(reducer, []);
22 return(
23 <SaladContext.Provider value={{ salad, setSalad }}>
24 <h1 className={classes.wrapper}>
25 <span role="img" aria-label="salad">🥗 </span>
26 Build Your Custom Salad!
27 <span role="img" aria-label="salad"> 🥗</span>
28 </h1>
29 <SaladBuilder />
30 <SaladSummary />
31 </SaladContext.Provider>
32 )
33}
保存和关闭文件. 与导航
一样,当SaladSummary
与文本相同时,创建一个背景可能看起来不必要。 将沙拉
作为一个插座是完全合理的,但您可能会最终重构它。
接下来,进入SaladItem
组件并将setSalad
函数从文本中拉出来。
在文本编辑器中打开组件:
1nano src/components/SaladItem/SaladItem.js
在SaladItem
内部,从SaladMaker
导入文本,然后使用破坏功能将setSalad
函数拉出来。 将setSalad
函数调用的按钮中添加一个点击事件。 由于您希望用户能够多次添加一个项目,您还需要为每个项目创建一个独特的ID,以便地图
函数能够分配一个独特的钥匙
:
1[label state-context-tutorial/src/components/SaladItem/SaladItem.js]
2import React, { useReducer, useContext } from 'react';
3import PropTypes from 'prop-types';
4import { createUseStyles } from 'react-jss';
5
6import UserContext from '../User/User';
7import { SaladContext } from '../SaladMaker/SaladMaker';
8
9const useStyles = createUseStyles({
10...
11});
12
13const reducer = key => key + 1;
14export default function SaladItem({ image, name }) {
15 const classes = useStyles();
16 const { setSalad } = useContext(SaladContext)
17 const user = useContext(UserContext);
18 const favorite = user.favorites.includes(name);
19 const [id, updateId] = useReducer(reducer, 0);
20 function update() {
21 setSalad({
22 name,
23 id: `${name}-${id}`
24 })
25 updateId();
26 };
27 return(
28 <div className={classes.wrapper}>
29 <h3>
30 {name}
31 </h3>
32 <span className={classes.favorite} aria-label={favorite ? 'Favorite' : 'Not Favorite'}>
33 {favorite ? '😋' : ''}
34 </span>
35 <button className={classes.add} onClick={update}>
36 <span className={classes.image} role="img" aria-label={name}>{image}</span>
37 </button>
38 </div>
39 )
40}
41...
要创建一个独特的ID,你会使用useReducer
来增加每个点击的值. 对于第一个点击,ID将是0
;第二个将是1
,等等. 你永远不会向用户显示这个值;这只会后来为地图函数创建一个独特的值。
创建独特ID后,您创建了一个名为更新
的函数,以增加ID并调用setSalad
。
保存和关闭文件. 最后一步是将动态数据从SaladSummary
中的背景中提取。
開啟「SaladSummary」的內容:
1nano src/components/SaladSummary/SaladSummary.js
导入SaladContext
组件,然后使用 destructuring提取salad
数据。 将硬编码的列表项目替换为将salad
转换为li
元素的函数。
1[label state-context-tutorial/src/components/SaladSummary/SaladSummary.js]
2import React, { useContext } from 'react';
3import { createUseStyles } from 'react-jss';
4
5import { SaladContext } from '../SaladMaker/SaladMaker';
6
7const useStyles = createUseStyles({
8...
9});
10
11export default function SaladSummary() {
12 const classes = useStyles();
13 const { salad } = useContext(SaladContext);
14 return(
15 <div className={classes.wrapper}>
16 <h2>Your Salad</h2>
17 <ul className={classes.list}>
18 {salad.map(({ name, id }) => (<li key={id}>{name}</li>))}
19 </ul>
20 </div>
21 )
22}
保存和关闭文件. 当你这样做时,你将能够点击项目,它将更新摘要:
注意背景如何让您能够在不同的组件中共享和更新数据。 背景并没有更新项目本身,但它为您提供了在多个组件上使用useReducer
的夹克的方法。 此外,您还可以自由地将背景放到树下。 似乎最好始终保持背景在根部,但通过保持背景更低,您不必担心未使用的状态在中央存储中停留。 一旦您卸载一个组件,数据就会消失。
在应用程序树中使用较低的背景的另一个优点是,你可以重复使用一个背景,而不必担心冲突。假设你有一个更大的应用程序,其中有一个三明治制造器和一个沙拉制造器。你可以创建一个名为订单背景
的通用背景,然后可以在组件中的多个点上使用它,而不必担心数据或名称冲突。
1| App
2 | Salads
3 | OrderContext
4 | SaladMaker
5 | Sandwiches
6 | OrderContext
7 | SandwichMaker
请注意,OrderContext
是两次存在的,这很好,因为useContext
将寻找最近的供应商。
在此步骤中,您使用语境共享和更新数据. 您还将语境放置在根元素之外,以便接近需要信息的组件,而无需混淆根元素。
结论
Context 是一个强大而灵活的工具,允许您在应用程序中存储和使用数据,并允许您使用内置的工具来处理分布式数据,而不需要任何额外的第三方安装或配置。
创建可重复使用的语境对于各种常见组件至关重要,例如需要访问元素数据的表单或需要标签视图的表单,这些视图对标签和显示都需要一个共同的语境。您可以在语境中存储多种类型的信息,包括主题、表单数据、警报消息等。
如果您想查看更多 React 教程,请查看我们的 React 主题页面,或返回 如何在 React.js 系列中编码页面。