如何利用上下文在 React 组件间共享状态

作者选择了 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 - 构建您的应用程序的基础

在此步骤中,您将构建您自定义沙拉构造器的总体结构。您将创建组件以显示可能的顶点、选定的顶点列表和客户信息. 当您使用静态数据构建应用程序时,您将发现如何在各种组件中使用不同的信息片段,以及如何识别在一个背景下有用的数据片段。

以下是您将构建的应用程序的例子:

Salad Builder Site

注意您可能需要在各组件中使用的信息如何。例如,用户名(此样本为 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;

保存和关闭文件. 当你这样做时,浏览器将更新,你会看到导航栏:

Navigation Bar

将导航栏视为一个 _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;

保存并关闭文件. 当您这样做时,页面将重新加载,您将看到标题:

Salad Maker Page

接下来,创建一个名为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}

保存和关闭文件. 当你这样做时,页面将重新加载,你会找到内容:

Salad Builder with Items

最后一步是添加正在进行的沙拉摘要. 此组件将显示用户选择的项目列表. 目前,您将硬编码这些项目。

在你的文本编辑器中打开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}

保存和关闭文件. 当你这样做时,页面将更新,你会找到完整的应用程序:

Salad Builder Site

导航组件和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,然后将NavigationSaladMaker包装到一个名为UserContext.Provider的组件中。请注意,在这种情况下,UserContext是作为标准的React组件的。

保存并关闭文件. 现在数据可在整个应用程序中使用。 但是,要使用数据,您需要再次导入并访问背景。

现在你已经设置了背景,你可以开始用动态值代替组件中的硬编码数据. 开始用你用UserContext.Provider设置的用户数据代替导航中的硬编码名称。

打开Navigation.js:

1nano src/components/Navigation/Navigation.js

导航中,从组件目录中导入 React 中的useContextUserContext。然后使用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}

UserContextApp.js中作为组件工作,但在这里你更像是一个数据片而使用它,但是如果你愿意的话,它仍然可以作为一个组件。你可以通过使用消费者作为用户背景的一部分访问相同的数据。你通过在你的JSX中添加UserContext.Consumer来获取数据,然后使用作为孩子访问数据的函数(https://reactjs.org/docs/context.html#contextconsumer)。

虽然使用消费者组件是可能的,但使用Hooks通常可以更短,更容易阅读,同时仍然提供相同的最新信息,这就是为什么本教程使用Hooks方法。

保存并关闭文件. 当你这样做时,页面将更新,你会看到相同的名称. 但这一次它已经动态更新:

Salad Builder Site

在这种情况下,数据不会穿过许多组件,代表数据穿过的路径的组件树看起来会是这样的:

1| UserContext.Provider
2  | Navigation

您可以将这个用户名作为一个贴纸,在这个规模上,这可能是一个有效的策略。但是随着应用程序的增长,有可能导航组件将移动。可能有一个名为头部的组件,包裹了导航组件和另一个组件,如标题栏,或者你会创建一个模板组件,然后在那里嵌入导航

下一个需要用户数据的组件是SaladItem组件.在SaladItem组件中,您将需要用户的喜好系列。

打开SaladItem.js:

1nano src/components/SaladItem/SaladItem.js

导入useContextUserContext,然后在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}

当你这样做时,浏览器会更新,你会看到只有最喜欢的项目有情绪:

Salad Maker with Avocado and Carrot favorited

导航不同,背景的旅行更远,组件树看起来像这样:

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。 如果你正在为相同的组件收集数据,你会使用useStateuseReducerHooks。 如果你对这些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}

保存和关闭文件. 当你这样做时,你将能够点击项目,它将更新摘要:

Adding salad items

注意背景如何让您能够在不同的组件中共享和更新数据。 背景并没有更新项目本身,但它为您提供了在多个组件上使用useReducer的夹克的方法。 此外,您还可以自由地将背景放到树下。 似乎最好始终保持背景在根部,但通过保持背景更低,您不必担心未使用的状态在中央存储中停留。 一旦您卸载一个组件,数据就会消失。

在应用程序树中使用较低的背景的另一个优点是,你可以重复使用一个背景,而不必担心冲突。假设你有一个更大的应用程序,其中有一个三明治制造器和一个沙拉制造器。你可以创建一个名为订单背景的通用背景,然后可以在组件中的多个点上使用它,而不必担心数据或名称冲突。

1| App
2  | Salads
3    | OrderContext
4      | SaladMaker
5  | Sandwiches
6    | OrderContext
7      | SandwichMaker

请注意,OrderContext是两次存在的,这很好,因为useContext将寻找最近的供应商。

在此步骤中,您使用语境共享和更新数据. 您还将语境放置在根元素之外,以便接近需要信息的组件,而无需混淆根元素。

结论

Context 是一个强大而灵活的工具,允许您在应用程序中存储和使用数据,并允许您使用内置的工具来处理分布式数据,而不需要任何额外的第三方安装或配置。

创建可重复使用的语境对于各种常见组件至关重要,例如需要访问元素数据的表单或需要标签视图的表单,这些视图对标签和显示都需要一个共同的语境。您可以在语境中存储多种类型的信息,包括主题、表单数据、警报消息等。

如果您想查看更多 React 教程,请查看我们的 React 主题页面,或返回 如何在 React.js 系列中编码页面

Published At
Categories with 技术
comments powered by Disqus