如何在 React 组件上使用钩子管理状态

作者选择了 Creative Commons以作为 Write for Donations计划的一部分获得捐赠。

介绍

React开发中,跟踪您的应用程序数据随着时间的推移如何变化被称为 state management. 通过管理应用程序的状态,您将能够创建响应用户输入的动态应用程序. 在 React 中,有许多方法来管理状态,包括 基于类的状态管理和第三方库,如 Redux. 在本教程中,您将使用一种方法管理功能组件的状态(由官方React文档鼓励)(https://reactjs.org/docs/hooks-faq.html#should-i-use-hooks-classes-or-a-mix-of-both):胡克。

由于这种状态管理方法不要求您使用类,开发人员可以使用 Hooks 来编写更短、更易阅读的代码,易于共享和维护。 Hooks 和基于类的状态管理的主要区别之一是没有一个单一的对象可以保留整个状态。

在本教程中,您将学习如何使用 useStateuseReducer) Hooks 设置状态。 在设置值时,不引用当前状态时,the useReducer Hook 是有用的,当您需要引用以前的值或当您有不同的操作需要复杂的数据操纵时。 为了探索这些不同设置状态的方式,您将创建一个产品页面组件,您将通过从选项列表中添加购物来更新。 在本教程结束时,您将舒适地使用 Hooks 来管理功能组件中的状态,并且您将有一个更先进的 Hooks 的基础,如 [useEffect(https://reactjs.org/docs/hooks-reference.html#useeffect), [Museemo(LINK3)),和 [use

前提条件

您将需要一个开发环境运行 Node.js;本教程已在 Node.js 版本 10.20.1 和 npm 版本 6.14.4 上测试;要在 macOS 或 Ubuntu 18.04 上安装此功能,请遵循 How to Install Node.js and Create a Local Development Environment on macOSHow to Install Node.js on Ubuntu 18.04的 [How to Install a PPA** section]的步骤。

步骤 1 – 在组件中设置初始状态

在此步骤中,您将通过将初始状态分配给一个自定义变量来设置组件的初始状态,使用useState夹克。 要探索 Hooks,您将创建一个产品页面,使用购物车,然后根据状态显示初始值。

首先,创建一个产品组件的目录:

1mkdir src/components/Product

接下来,在产品目录中打开名为Product.js的文件:

1nano src/components/Product/Product.js

该组件将由两个部分组成:篮子,其中有项目数量和总价格,以及产品,其中有一个按钮来添加或删除该项目从篮子里。

将以下代码添加到文件中:

 1[label hooks-tutorial/src/components/Product/Product.js]
 2import React from 'react';
 3import './Product.css';
 4
 5export default function Product() {
 6  return(
 7    <div className="wrapper">
 8      <div>
 9        Shopping Cart: 0 total items.
10      </div>
11      <div>Total: 0</div>
12
13      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
14      <button>Add</button> <button>Remove</button>
15    </div>
16  )
17}

在此代码中,您使用了 JSX来创建产品组件的HTML元素,并使用冰淇淋符号来表示产品。

保存并关闭文件,然后在产品目录中创建一个名为Product.css的新文件:

1nano src/components/Product/Product.css

添加一些风格来增加文本和情感符的字体大小:

 1[label hooks-tutorial/src/components/Product/Product.css]
 2.product span {
 3    font-size: 100px;
 4}
 5
 6.wrapper {
 7    padding: 20px;
 8    font-size: 20px;
 9}
10
11.wrapper button {
12    font-size: 20px;
13    background: none;
14    border: black solid 1px;
15}

Emoji 将需要一个更大的字体大小,因为它作为产品图像,此外,你正在通过将背景设置为没有来删除按钮上的默认梯度背景。

保存并关闭文件. 现在,将该组件添加到应用组件中,以在浏览器中返回产品组件。

1nano src/components/App/App.js

此外,删除CSS导入,因为你不会在本教程中使用它:

1[label hooks-tutorial/src/components/App/App.js]
2import React from 'react';
3import Product from '../Product/Product';
4
5function App() {
6  return <Product />
7}
8
9export default App;

当你这样做时,浏览器会更新,你会看到产品组件:

Product Page

现在你有一个工作组件,你可以用动态值代替硬编码的数据。

React 导出多个 Hooks,您可以直接从主React包导入。 根据惯例,React Hooks 以useState,useContextuseReducer等词开始,大多数第三方库都遵循相同的惯例。

Hooks 是允许您在 React lifecycle中运行操作的函数。 Hooks 是由其他动作或组件的附加功能发生的,用来创建数据或触发进一步的更改。例如,useState Hook 会生成一个状态式的数据块,以及一个用于改变该数据块并触发重新渲染的函数。它会创建一个动态的代码块,并通过触发数据更改时的再渲染来插入生命周期。

例如,在这个组件中,你有两个数据,根据用户的操作会发生变化:篮子和总成本。

若要尝试,请打开Product.js:

1nano src/components/Product/Product.js

接下来,通过添加突出的代码从 React 导入 useState Hook:

 1[label hooks-tutorial/src/components/Product/Product.js]
 2import React, { useState } from 'react';
 3import './Product.css';
 4
 5export default function Product() {
 6  return(
 7    <div className="wrapper">
 8      <div>
 9        Shopping Cart: 0 total items.
10      </div>
11      <div>Total: 0</div>
12
13      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
14      <button>Add</button> <button>Remove</button>
15    </div>
16  )
17}

useState是一个函数,它将初始状态作为一个参数,并返回两个元素的 array。第一个元素是包含状态的变量,您通常会在 JSX 中使用它。数组中的第二个元素是会更新状态的函数。因为 React 将数据返回作为一个数组,您可以使用 destructuring将值分配给您想要的任何变量名称。

创建你的第一个胡克,用一个空的数组召唤useState胡克,添加以下突出的代码:

 1[label hooks-tutorial/src/components/Product/Product.js]
 2import React, { useState } from 'react';
 3import './Product.css';
 4
 5export default function Product() {
 6  const [cart, setCart] = useState([]);
 7  return(
 8    <div className="wrapper">
 9      <div>
10        Shopping Cart: {cart.length} total items.
11      </div>
12      <div>Total: 0</div>
13
14      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
15      <button>Add</button> <button>Remove</button>
16    </div>
17  )
18}

在这里,您将第一个值,状态,分配给名为cart的变量。cart将是一个包含篮子中的产品的数组. 通过将一个空数组作为一个参数useState,您将最初的空状态设置为cart的第一个值。

除了cart变量之外,您还将更新函数分配给一个名为setCart的变量,此时您不使用setCart函数,您可能会看到一个关于未使用的变量的警告。

当浏览器重新加载时,您将看到页面没有更改:

Product Page

Hooks 和基于类的状态管理之间的一个重要区别是,在基于类的状态管理中,有一个单一的状态对象。在 Hooks 中,状态对象是完全独立的,所以你可以有尽可能多的状态对象。

Product.js中,尝试创建一个新的状态,以保持总数,设置默认值为0,并将值和函数分配到总数设置总数:

 1[label hooks-tutorial/src/components/Product/Product.js]
 2import React, { useState } from 'react';
 3import './Product.css';
 4
 5export default function Product() {
 6  const [cart, setCart] = useState([]);
 7  const [total, setTotal] = useState(0);
 8  return(
 9    <div className="wrapper">
10      <div>
11        Shopping Cart: {cart.length} total items.
12      </div>
13      <div>Total: {total}</div>
14
15      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
16      <button>Add</button> <button>Remove</button>
17    </div>
18  )
19}

现在你有一些状态数据,你可以将显示的数据标准化,以使体验更加可预测。例如,由于本示例中的总数是价格,它将始终有两个十进制位置。 您可以使用 toLocaleString 方法将‘总数’从一个数字转换为具有两个十进制位置的字符串。 它还会根据与浏览器的本地相匹配的数字惯例将数字转换为字符串。

创建一个名为getTotal的函数. 此函数将使用范围变量total并返回一个本地化字符串,您将使用它来显示总数。

 1[label hooks-tutorial/src/components/Product/Product.js]
 2import React, { useState } from 'react';
 3import './Product.css';
 4
 5const currencyOptions = {
 6  minimumFractionDigits: 2,
 7  maximumFractionDigits: 2,
 8}
 9
10export default function Product() {
11  const [cart, setCart] = useState([]);
12  const [total, setTotal] = useState(0);
13
14  function getTotal() {
15    return total.toLocaleString(undefined, currencyOptions)
16  }
17
18  return(
19    <div className="wrapper">
20      <div>
21        Shopping Cart: {cart.length} total items.
22      </div>
23      <div>Total: {getTotal()}</div>
24
25      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
26      <button>Add</button> <button>Remove</button>
27    </div>
28  )
29}

虽然getTotal是一个单独的函数,但它与周围函数共享相同的范围,这意味着它可以参考组件的变量。

该页面将重新加载,您将看到更新的总数,其中有两个十进制位置:

Price converted to decimal

这个函数起作用,但从现在开始,getTotal只能在这个代码中运作。在这种情况下,你可以将其转换为纯函数,该函数在给出相同输入时会产生相同的输出,而不依赖某个特定环境来运作。

更新getTotal以将total作为参数,然后将函数移动到组件之外:

 1[label hooks-tutorial/src/components/Product/Product.js]
 2import React, { useState } from 'react';
 3import './Product.css';
 4
 5const currencyOptions = {
 6  minimumFractionDigits: 2,
 7  maximumFractionDigits: 2,
 8}
 9
10function getTotal(total) {
11  return total.toLocaleString(undefined, currencyOptions)
12}
13
14export default function Product() {
15  const [cart, setCart] = useState([]);
16  const [total, setTotal] = useState(0);
17
18  return(
19    <div className="wrapper">
20      <div>
21        Shopping Cart: {cart.length} total items.
22      </div>
23      <div>Total: {getTotal(total)}</div>
24
25      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
26      <button>Add</button> <button>Remove</button>
27    </div>
28  )
29}

保存文件. 当您这样做时,页面将重新加载,您将看到元件就像以前一样。

像这样的功能组件可以更容易地移动函数,只要没有范围冲突,您可以将这些转换函数移动到您想要的任何地方。

在此步骤中,您使用useState设置了状态数据的默认值,然后保存了状态数据和使用数组破坏来更新状态的变量函数。

步骤 2 – 用useState设置状态

在此步骤中,您将通过设置具有静态值的新状态来更新您的产品页面. 您已经创建了更新状态的函数,所以现在您将创建一个事件以更新两个状态变量以预定义值。

与基于类的组件不同,您无法通过单个函数调用更新多个状态部分,而您必须单独调用每个函数,这意味着问题分离更大,这有助于保持状态对象的聚焦。

创建一个函数,将项目添加到篮子中,并用该项目的价格更新总和,然后将该功能添加到 ** 添加** 按钮:

 1[label hooks-tutorial/src/components/Product/Product.js]
 2import React, { useState } from 'react';
 3
 4...
 5
 6export default function Product() {
 7  const [cart, setCart] = useState([]);
 8  const [total, setTotal] = useState(0);
 9
10  function add() {
11    setCart(['ice cream']);
12    setTotal(5);
13  }
14
15  return(
16    <div className="wrapper">
17      <div>
18        Shopping Cart: {cart.length} total items.
19      </div>
20      <div>Total: {getTotal(total)}</div>
21
22      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
23      <button onClick={add}>Add</button>
24      <button>Remove</button>
25    </div>
26  )
27}

在此片段中,您将setCart称为包含冰淇淋一组,并将setTotal称为5

请注意,该函数必须具有与设置状态的函数相同的范围,因此它必须在组件函数内部定义。

当你这样做时,浏览器将重新加载,当你点击添加按钮时,篮子将更新当前的金额:

Click on the button and see state updated

既然你不是引用这个的背景,你可以使用箭头函数(https://andsky.com/tech/tutorials/how-to-define-functions-in-javascript#arrow-functions)或函数声明,这两种函数在这里工作同样顺利,每个开发人员或团队都可以决定使用哪种风格,你甚至可以跳过定义一个额外的函数,并将该函数直接传入点击属性。

要尝试,创建一个函数来删除值,将篮子设置为空对象,总数为 `0。

 1[label hooks-tutorial/src/component/Product/Product.js]
 2import React, { useState } from 'react';
 3...
 4export default function Product() {
 5  const [cart, setCart] = useState([]);
 6  const [total, setTotal] = useState(0);
 7
 8  function add() {
 9    setCart(['ice cream']);
10    setTotal(5);
11  }
12
13  return(
14    <div className="wrapper">
15      <div>
16        Shopping Cart: {cart.length} total items.
17      </div>
18      <div>Total: {getTotal(total)}</div>
19
20      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
21      <button onClick={add}>Add</button>
22      <button
23        onClick={() => {
24          setCart([]);
25          setTotal(0);
26        }}
27      >
28        Remove
29      </button>
30    </div>
31  )
32}

保存文件. 完成后,您将能够添加和删除一个项目:

Add and Remove

在每个重新渲染中,React将创建一个新函数,这将触发一个PRO变更,并导致组件重新渲染。当您定义一个函数在PRO之外时,您可以利用另一个名为useCallback的(LINK0)来创建一个箭头函数。这将 _memoize_该函数,这意味着它只会创建一个新函数,如果某些值发生变化。如果没有任何变化,该程序将使用该函数的缓存,而不是重新计算它。

在此步骤中,您将状态数据更新为由useState Hook 创建的函数。您创建了包装函数以调用两个函数同时更新多个数据的状态,但这些函数是有限的,因为它们添加了静态、预定义的值,而不是使用以前的状态创建新状态。

步骤 3 – 使用当前状态设置状态

在之前的步骤中,您更新了状态以静态值。不论以前的状态是什么,您总是通过相同的值,但典型的产品页面将包含许多项目,您可以添加到篮子中,并且您希望能够更新篮子,同时保留以前的项目。

在此步骤中,您将使用当前状态更新状态. 您将扩展您的产品页面以包括多个产品,并创建功能,根据当前值更新篮子和总数。

由于 React 可以通过非同步调用操作来优化代码,所以您需要确保您的函数可以访问最新的状态。 解决此问题的最基本方法是将函数传递给状态设置函数,而不是值。

要开始实施此项,请通过创建一个产品对象(https://andsky.com/tech/tutorials/understanding-objects-in-javascript)的数组来添加一些更多项目到产品页面,然后从 ** 添加** 和 ** 删除** 按钮中删除事件处理器,以便进行重构:

 1[label hooks-tutorial/src/component/Product/Product.js]
 2import React, { useState } from 'react';
 3import './Product.css';
 4
 5...
 6
 7const products = [
 8  {
 9    emoji: '🍦',
10    name: 'ice cream',
11    price: 5
12  },
13  {
14    emoji: '🍩',
15    name: 'donuts',
16    price: 2.5,
17  },
18  {
19    emoji: '🍉',
20    name: 'watermelon',
21    price: 4
22  }
23];
24
25export default function Product() {
26  const [cart, setCart] = useState([]);
27  const [total, setTotal] = useState(0);
28
29  function add() {
30    setCart(['ice cream']);
31    setTotal(5);
32  }
33
34  return(
35    <div className="wrapper">
36      <div>
37        Shopping Cart: {cart.length} total items.
38      </div>
39      <div>Total: {getTotal(total)}</div>
40        <div>
41        {products.map(product => (
42          <div key={product.name}>
43            <div className="product">
44              <span role="img" aria-label={product.name}>{product.emoji}</span>
45            </div>
46            <button>Add</button>
47            <button>Remove</button>
48          </div>
49        ))}
50      </div><^
51    </div>
52  )
53}

现在你有一些 JSX 使用 .map 方法在数组上迭代并显示产品。

当你这样做时,页面将重新加载,你会看到多个产品:

Product list

目前,按钮没有任何操作. 由于您只想在点击时添加特定产品,您需要将该产品作为参数传递给添加函数。 在添加函数中,而不是将新项目直接传递给setCartsetTotal函数,您将传递一个匿名函数,该函数将取代当前状态并返回新的更新值:

 1[label hooks-tutorial/src/component/Product/Product.js]
 2import React, { useState } from 'react';
 3import './Product.css';
 4...
 5export default function Product() {
 6  const [cart, setCart] = useState([]);
 7  const [total, setTotal] = useState(0);
 8
 9  function add(product) {
10    setCart(current => [...current, product.name]);
11    setTotal(current => current + product.price);
12  }
13
14  return(
15    <div className="wrapper">
16      <div>
17        Shopping Cart: {cart.length} total items.
18      </div>
19      <div>Total: {getTotal(total)}</div>
20
21      <div>
22        {products.map(product => (
23          <div key={product.name}>
24            <div className="product">
25              <span role="img" aria-label={product.name}>{product.emoji}</span>
26            </div>
27            <button onClick={() => add(product)}>Add</button>
28            <button>Remove</button>
29          </div>
30        ))}
31      </div>
32    </div>
33  )
34}

匿名函数使用最新的状态 - 无论是cart还是total - 作为一个参数,您可以使用它来创建一个新的值。 但是,请注意不要直接改变状态。

当你这样做时,浏览器将重新加载,你将能够添加多个产品:

Adding products

有另一个名为 useReducer的胡克,专门用于根据当前状态更新状态,类似于 .reduce 数组方法useReducer 胡克类似于 `useState,但当您初始化胡克时,您将传入一个函数,胡克将运行,当您与初始数据一起更改状态。

重定义车库状态以使用useReducer。 创建一个名为cartReducer的函数,以状态产品为参数。 将useState替换为useReducer,然后将cartReducer函数作为第一个参数和一个空数作为第二个参数,这将是初始数据:

 1[label hooks-tutorial/src/component/Product/Product.js]
 2import React, { useReducer, useState } from 'react';
 3
 4...
 5
 6function cartReducer(state, product) {
 7  return [...state, product]
 8}
 9
10export default function Product() {
11  const [cart, setCart] = useReducer(cartReducer, []);
12  const [total, setTotal] = useState(0);
13
14  function add(product) {
15    setCart(product.name);
16    setTotal(current => current + product.price);
17  }
18
19  return(
20...
21  )
22}

现在,当您调用setCart时,请输入产品名称而不是函数。当您调用setCart时,您将调用减量函数,而产品将是第二个参数。

创建一个名为TotalReducer的函数,该函数采用当前状态并添加新的数额,然后用useState代替useReducer,然后代替函数代替新的值setCart:

 1[label hooks-tutorial/src/component/Product/Product.js]
 2import React, { useReducer } from 'react';
 3
 4...
 5
 6function totalReducer(state, price) {
 7  return state + price;
 8}
 9
10export default function Product() {
11  const [cart, setCart] = useReducer(cartReducer, []);
12  const [total, setTotal] = useReducer(totalReducer, 0);
13
14  function add(product) {
15    setCart(product.name);
16    setTotal(product.price);
17  }
18
19  return(
20    ...
21  )
22}

由于您不再使用useState链接,您已从导入中删除它。

当你这样做时,页面将重新加载,你将能够添加到篮子中的项目:

Adding products

现在是时候添加删除函数了,但这会导致一个问题:减速函数可以处理添加项目和更新总数,但尚不清楚它将如何处理从状态中删除项目。减速函数中的一种常见模式是将一个对象传递为包含行动名称和行动数据的第二个参数。在减速器内部,您可以根据行动更新总数。在这种情况下,您将在添加操作中添加项目到篮子,并在删除操作中删除它们。

更新函数以行动作为第二个参数,然后添加一个 条件以根据action.type更新状态:

 1[label hooks-tutorial/src/component/Product/Product.js]
 2import React, { useReducer } from 'react';
 3import './Product.css';
 4
 5...
 6
 7function totalReducer(state, action) {
 8  if(action.type === 'add') {
 9    return state + action.price;
10  }
11  return state - action.price
12}
13
14export default function Product() {
15  const [cart, setCart] = useReducer(cartReducer, []);
16  const [total, setTotal] = useReducer(totalReducer, 0);
17
18  function add(product) {
19    const { name, price } = product;
20    setCart(name);
21    setTotal({ price, type: 'add' });
22  }
23
24  return(
25    ...
26  )
27}

行动是一个具有两种属性的对象:类型价格。类型可以是添加删除,而价格是一个数字,如果类型是添加,则增加总数,如果是删除,则降低总数。

接下来,您将更新cartReducer。这个更为复杂:您可以使用如果/然后条件,但更常见的是使用交换声明(https://andsky.com/tech/tutorials/how-to-use-the-switch-statement-in-javascript)。

TotalReducer一样,你会将一个对象作为第二个对象的类型名称属性传递。

更新cartReducer后,创建一个删除函数,称为setCartsetTotal,其中包含包含类型:删除价格名称`的对象。

 1[label hooks-tutorial/src/complicated/Product/Product.js]
 2import React, { useReducer } from 'react';
 3import './Product.css';
 4
 5...
 6
 7function cartReducer(state, action) {
 8  switch(action.type) {
 9    case 'add':
10      return [...state, action.name];
11    case 'remove':
12      const update = [...state];
13      update.splice(update.indexOf(action.name), 1);
14      return update;
15    default:
16      return state;
17  }
18}
19
20function totalReducer(state, action) {
21  if(action.type === 'add') {
22    return state + action.price;
23  }
24  return state - action.price
25}
26
27export default function Product() {
28  const [cart, setCart] = useReducer(cartReducer, []);
29  const [total, setTotal] = useReducer(totalReducer, 0);
30
31  function add(product) {
32    const { name, price } = product;
33    setCart({ name, type: 'add' });
34    setTotal({ price, type: 'add' });
35  }
36
37  function remove(product) {
38    const { name, price } = product;
39    setCart({ name, type: 'remove' });
40    setTotal({ price, type: 'remove' });
41  }
42
43  return(
44    <div className="wrapper">
45      <div>
46        Shopping Cart: {cart.length} total items.
47      </div>
48      <div>Total: {getTotal(total)}</div>
49
50      <div>
51        {products.map(product => (
52          <div key={product.name}>
53            <div className="product">
54              <span role="img" aria-label={product.name}>{product.emoji}</span>
55            </div>
56            <button onClick={() => add(product)}>Add</button>
57            <button onClick={() => remove(product)}>Remove</button>
58          </div>
59        ))}
60      </div>
61    </div>
62  )
63}

当你在代码上工作时,要小心不要直接改变减速器函数中的状态。相反,在splicing对象之前,做一份副本。 另外,请注意,在交换语句上添加一个默认操作是最好的做法,以便对不可预测的边缘事件进行回应。 在这种情况下,只需返回对象。 其他默认选项是抛出错误或返回到添加或删除等操作。

更改后,保存文件. 当浏览器更新时,您将能够添加和删除项目:

Remove items

删除方法中,您可以从价格中扣除,即使该项不在购物车中。如果您点击删除冰淇淋而不添加到购物车中,则显示的总值将为 -5.00

您可以通过检查某个项目是否存在之前删除它来修复此错误,但更有效的方法是通过只将相关数据保存到一个地方来最大限度地减少不同的状态部分。

重构组件,以便 add() 函数将整个产品传递到减量器中,而 remove() 函数将删除整个对象。 getTotal 方法将使用篮子,因此您可以删除 totalReducer 函数。

 1[label hooks-tutorial/src/component/Product/Product.js]
 2import React, { useReducer } from 'react';
 3import './Product.css';
 4
 5const currencyOptions = {
 6  minimumFractionDigits: 2,
 7  maximumFractionDigits: 2,
 8}
 9
10function getTotal(cart) {
11  const total = cart.reduce((totalCost, item) => totalCost + item.price, 0);
12  return total.toLocaleString(undefined, currencyOptions)
13}
14
15...
16
17function cartReducer(state, action) {
18  switch(action.type) {
19    case 'add':
20      return [...state, action.product];
21    case 'remove':
22      const productIndex = state.findIndex(item => item.name === action.product.name);
23      if(productIndex < 0) {
24        return state;
25      }
26      const update = [...state];
27      update.splice(productIndex, 1)
28      return update
29    default:
30      return state;
31  }
32}
33
34export default function Product() {
35  const [cart, setCart] = useReducer(cartReducer, []);
36
37  function add(product) {
38    setCart({ product, type: 'add' });
39  }
40
41  function remove(product) {
42    setCart({ product, type: 'remove' });
43  } 
44
45  return(
46    <div className="wrapper">
47      <div>
48        Shopping Cart: {cart.length} total items.
49      </div>
50      <div>Total: {getTotal(cart)}</div>
51
52      <div>
53        {products.map(product => (
54          <div key={product.name}>
55            <div className="product">
56              <span role="img" aria-label={product.name}>{product.emoji}</span>
57            </div>
58            <button onClick={() => add(product)}>Add</button>
59            <button onClick={() => remove(product)}>Remove</button>
60          </div>
61        ))}
62      </div>
63    </div>
64  )
65}

当你这样做时,浏览器将更新,你将有你的最后的篮子:

Add and remove products

通过使用 useReducer Hook,您保持了主要组件的组织和可读性,因为对数组进行解析和合并的复杂逻辑在组件之外。如果您想要重复使用,您还可以将减量器移动到组件外部,或者您可以创建一个自定义的胡克,以便在多个组件中使用。

Hooks 让您有机会将状态式逻辑移动到组件内外,而不是类,在那里您通常与组件绑定。 这种优势也可以扩展到其他组件中。

在此步骤中,您学会了使用当前状态设置状态. 您创建了一个组件,使用useStateuseReducer链接更新状态,并将该组件重新定义为不同的链接,以防止错误并提高可重复使用性。

结论

Hooks 是 React 的一个重大变化,它创造了一个新的方式来共享逻辑和更新组件而不使用类. 现在你可以使用useStateuseReducer创建组件,你有工具来创建响应用户和动态信息的复杂项目。

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

Published At
Categories with 技术
comments powered by Disqus