react hooks使用_如何使用React组件上的Hooks管理状态

react hooks使用

The author selected Creative Commons to receive a donation as part of the Write for DOnations program.

作者选择了创用CC来接受捐赠,这是Write for DOnations计划的一部分。

介绍 (Introduction)

In React development, keeping track of how your application data changes over time is called state management. By managing the state of your application, you will be able to make dynamic apps that respond to user input. There are many methods of managing state in React, including class-based state management and third-party libraries like Redux. In this tutorial, you’ll manage state on functional components using a method encouraged by the official React documentation: Hooks.

React开发中,跟踪应用程序数据随时间变化的方式称为状态管理 。 通过管理应用程序的状态,您将能够制作响应用户输入的动态应用程序。 React中有许多管理状态的方法,包括基于类的状态管理Redux等第三方库。 在本教程中,您将使用官方React文档鼓励的方法Hooks管理功能组件上的状态。

Hooks are a broad set of tools that run custom functions when a component’s props change. Since this method of state management doesn’t require you to use classes, developers can use Hooks to write shorter, more readable code that is easy to share and maintain. One of the main differences between Hooks and class-based state management is that there is no single object that holds all of the state. Instead, you can break up state into multiple pieces that you can update independently.

挂钩是一组广泛的工具,可在组件的props更改时运行自定义功能。 由于这种状态管理方法不需要您使用类,因此开发人员可以使用Hooks编写更短,更易读的代码,这些代码易于共享和维护。 Hook和基于类的状态管理之间的主要区别之一是,没有单个对象可以保存所有状态。 相反,您可以将状态分为多个部分,可以独立更新。

Throughout this tutorial, you’ll learn how to set state using the useState and useReducer Hooks. The useState Hook is valuable when setting a value without referencing the current state; the useReducer Hook is useful when you need to reference a previous value or when you have different actions the require complex data manipulations. To explore these different ways of setting state, you’ll create a product page component with a shopping cart that you’ll update by adding purchases from a list of options. By the end of this tutorial, you’ll be comfortable managing state in a functional component using Hooks, and you’ll have a foundation for more advanced Hooks such as useEffect, useMemo, and useContext.

在本教程中,您将学习如何使用useStateuseReducer Hooks设置状态。 当设置一个值而不引用当前状态时, useState Hook很有用。 当您需要引用先前的值或执行不同的操作需要复杂的数据操作时, useReducer挂钩非常有用。 为了探索这些不同的状态设置方法,您将创建一个带有购物车的产品页面组件,然后通过从选项列表中添加购买来更新购物车。 在本教程结束时,您将习惯于使用Hooks在功能组件中管理状态,并且为更高级的Hook(例如useEffectuseMemouseContext奠定了基础。

先决条件 (Prerequisites)

步骤1 –在组件中设置初始状态 (Step 1 – Setting Initial State in a Component)

In this step, you’ll set the initial state on a component by assigning the initial state to a custom variable using the useState Hook. To explore Hooks, you’ll make a product page with a shopping cart, then display the initial values based on the state. By the end of the step, you’ll know the different ways to hold a state value using Hooks and when to use state rather than a prop or a static value.

在此步骤中,您将使用useState Hook将初始状态分配给自定义变量, useState在组件上设置初始状态。 要浏览钩子,您将使用购物车制作一个产品页面,然后根据状态显示初始值。 在该步骤的最后,您将了解使用Hooks持有状态值的不同方法,以及何时使用状态而不是prop或静态值。

Start by creating a directory for a Product component:

首先为Product组件创建目录:

  • mkdir src/components/Product

    mkdir src /组件/产品

Next, open up a file called Product.js in the Product directory:

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

  • nano src/components/Product/Product.js

    纳米src / components / Product / Product.js

Start by creating a component with no state. The component will consist of two parts: the cart, which has the number of items and the total price, and the product, which has a button to add or remove the item from the cart. For now, these buttons will have no function.

首先创建一个没有状态的组件 。 该组件将由两部分组成:具有商品数量和总价格的购物车,以及具有添加或从购物车中删除商品的按钮的产品。 目前,这些按钮将不起作用。

Add the following code to the file:

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

hooks-tutorial/src/components/Product/Product.js
hooks-tutorial / src / components / Product / Product.js
import React from 'react';
import './Product.css';

export default function Product() {
  return(
    <div className="wrapper">
      <div>
        Shopping Cart: 0 total items.
      </div>
      <div>Total: 0</div>

      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
      <button>Add</button> <button>Remove</button>
    </div>
  )
}

In this code, you used JSX to create the HTML elements for the Product component, with an ice cream emoji to represent the product. In addition, two of the <div> elements have class names so you can add some basic CSS styling.

在此代码中,您使用JSX创建了Product组件HTML元素,并用冰淇淋表情符号代表了产品。 另外,两个<div>元素具有类名,因此您可以添加一些基本CSS样式。

Save and close the file, then create a new file called Product.css in the Product directory:

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

  • nano src/components/Product/Product.css

    纳米src / components / Product / Product.css

Add some styling to increase the font size for the text and the emoji:

添加一些样式以增加文本和表情符号的字体大小:

hooks-tutorial/src/components/Product/Product.css
hooks-tutorial / src / components / Product / Product.css
.product span {
    font-size: 100px;
}

.wrapper {
    padding: 20px;
    font-size: 20px;
}

.wrapper button {
    font-size: 20px;
    background: none;
    border: black solid 1px;
}

The emoji will need a much larger font-size, since it’s acting as the product image. In addition, you are removing the default gradient background on the button by setting background to none.

表情符号将需要更大的font-size ,因为它充当产品图片。 此外,您可以通过将background设置为none来删除按钮上的默认渐变背景。

Save and close the file. Now, add the component into the App component to render the Product component in the browser. Open App.js:

保存并关闭文件。 现在,将组件添加到App组件中以在浏览器中呈现Product组件。 打开App.js

  • nano src/components/App/App.js

    纳米src / components / App / App.js

Import the component and render it. Also, delete the CSS import since you won’t be using it in this tutorial:

导入组件并渲染它。 另外,删除CSS导入,因为在本教程中将不使用它:

hooks-tutorial/src/components/App/App.js
hooks-tutorial / src / components / App / App.js
import React from 'react';
import Product from '../Product/Product';

function App() {
  return <Product />
}

export default App;

Save and close the file. When you do, the browser will refresh and you’ll see the Product component:

保存并关闭文件。 完成后,浏览器将刷新,您将看到“ Product组件:

Now that you have a working component, you can replace the hard-coded data with dynamic values.

现在您有了一个有效的组件,您可以用动态值替换硬编码的数据了。

React exports several Hooks that you can import directly from the main React package. By convention, React Hooks start with the word use, such as useState, useContext, and useReducer. Most third-party libraries follow the same convention. For example, Redux has a useSelector and a useStore Hook.

React导出了几个Hook ,您可以直接从主React包中导入它们。 按照约定,React Hooks以单词use开头,例如useStateuseContextuseReducer 。 大多数第三方库都遵循相同的约定。 例如, Redux具有useSelectoruseStore Hook

Hooks are functions that let you run actions as part of the React lifecycle. Hooks are triggered either by other actions or by changes in a component’s props and are used to either create data or to trigger further changes. For example, the useState Hook generates a stateful piece of data along with a function for changing that piece of data and triggering a re-render. It will create a dynamic piece of code and hook into the lifecycle by triggering re-renders when the data changes. In practice, that means you can store dynamic pieces of data in variables using the useState Hook.

挂钩是使您能够在React生命周期中运行操作的函数。 挂钩是由其他动作或组件属性的更改触发的,用于创建数据或触发进一步的更改。 例如, useState Hook生成一个有状态的数据以及用于更改该数据并触发重新渲染的函数。 它将创建动态代码段,并在数据更改时触发重新渲染,从而进入生命周期。 实际上,这意味着您可以使用useState Hook将动态数据存储在变量中。

For example, in this component, you have two pieces of data that will change based on user actions: the cart and the total cost. Each of these can be stored in state using the above Hook.

例如,在此组件中,您有两个数据将根据用户操作而改变:购物车和总成本。 可以使用上面的Hook将这些状态存储在其中。

To try this out, open up Product.js:

要尝试此操作,请打开Product.js

  • nano src/components/Product/Product.js

    纳米src / components / Product / Product.js

Next, import the useState Hook from React by adding the highlighted code:

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

hooks-tutorial/src/components/Product/Product.js
hooks-tutorial / src / components / Product / Product.js
import React, { useState } from 'react';
import './Product.css';

export default function Product() {
  return(
    <div className="wrapper">
      <div>
        Shopping Cart: 0 total items.
      </div>
      <div>Total: 0</div>

      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
      <button>Add</button> <button>Remove</button>
    </div>
  )
}

useState is a function that takes the initial state as an argument and returns an array with two items. The first item is a variable containing the state, which you will often use in your JSX. The second item in the array is a function that will update the state. Since React returns the data as an array, you can use destructuring to assign the values to any variable names you want. That means you can call useState many times and never have to worry about name conflicts, since you can assign every piece of state and update function to a clearly named variable.

useState是一个函数,该函数将初始状态作为参数并返回包含两个项目的数组 。 第一项是包含状态的变量,您经常在JSX中使用该状态。 数组中的第二项是将更新状态的函数。 由于React将数据作为数组返回,因此您可以使用解构将值分配给所需的任何变量名称。 这意味着您可以多次调用useState ,而不必担心名称冲突,因为您可以将每一个状态分配给更新的函数,并将函数更新为明确命名的变量。

Create your first Hook by invoking the useState Hook with an empty array. Add in the following highlighted code:

通过使用一个空数组调用useState Hook来创建您的第一个Hook。 添加以下突出显示的代码:

hooks-tutorial/src/components/Product/Product.js
hooks-tutorial / src / components / Product / Product.js
import React, { useState } from 'react';
import './Product.css';

export default function Product() {
  const [cart, setCart] = useState([]);
  return(
    <div className="wrapper">
      <div>
        Shopping Cart: {cart.length} total items.
      </div>
      <div>Total: 0</div>

      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
      <button>Add</button> <button>Remove</button>
    </div>
  )
}

Here you assigned the first value, the state, to a variable called cart. cart will be an array that contains the products in the cart. By passing an empty array as an argument to useState, you set the initial empty state as the first value of cart.

在这里,您将第一个值(状态)分配给了一个名为cart的变量。 cart将是一个包含购物车中产品的数组。 通过使空数组作为参数传递给useState ,则设置初始空状态的第一个值cart

In addition to the cart variable, you assigned the update function to a variable called setCart. At this point, you aren’t using the setCart function, and you may see a warning about having an unused variable. Ignore this warning for now; in the next step, you’ll use setCart to update the cart state.

除了cart变量之外,您setCart更新功能分配给了一个名为setCart的变量。 此时,您尚未使用setCart函数,并且可能会看到有关未使用变量的警告。 现在忽略此警告; 在下一步中,您将使用setCart更新cart状态。

Save the file. When the browser reloads, you’ll see the page without changes:

保存文件。 当浏览器重新加载时,您会看到页面没有更改:

One important difference between Hooks and class-based state management is that, in class-based state management, there is a single state object. With Hooks, state objects are completely independent of each other, so you can have as many state objects as you want. That means that if you want a new piece of stateful data, all you need to do is call useState with a new default and assign the result to new variables.

Hooks与基于类的状态管理之间的一个重要区别是,在基于类的状态管理中,只有一个状态对象。 使用Hook,状态对象彼此完全独立,因此您可以根据需要拥有任意数量的状态对象。 这意味着,如果您想要一块新的有状态数据,您要做的就是用新的默认值调用useState并将结果分配给新变量。

Inside Product.js, try this out by creating a new piece of state to hold the total. Set the default value to 0 and assign the value and function to total and setTotal:

Product.js ,通过创建一个新的状态来保存total来进行尝试。 将默认值设置为0并将值和函数分配给totalsetTotal

hooks-tutorial/src/components/Product/Product.js
hooks-tutorial / src / components / Product / Product.js
import React, { useState } from 'react';
import './Product.css';

export default function Product() {
  const [cart, setCart] = useState([]);
  const [total, setTotal] = useState(0);
  return(
    <div className="wrapper">
      <div>
        Shopping Cart: {cart.length} total items.
      </div>
      <div>Total: {total}</div>

      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
      <button>Add</button> <button>Remove</button>
    </div>
  )
}

Now that you have some stateful data, you can standardize the displayed data to make a more predictable experience. For example, since the total in this example is a price, it will always have two decimal places. You can use the toLocaleString method to convert total from a number to a string with two decimal places. It will also convert the number to a string according to the numerical conventions that match the browser’s locale. You’ll set the options minimumFractionDigits and maximumFractionDigits to give a consistent number of decimal places.

现在,您已经有了一些状态数据,您可以标准化显示的数据,以提供更可预测的体验。 例如,由于此示例中的总计是价格,因此它将始终有两个小数位。 您可以使用toLocaleString方法将total从数字转换为两位小数的字符串。 还将根据与浏览器区域设置匹配的数字约定将数字转换为字符串。 您可以设置选项minimumFractionDigitsmaximumFractionDigits给小数的数一致。

Create a function called getTotal. This function will use the in-scope variable total and return a localized string that you will use to display the total. Use undefined as the first argument to toLocaleString to use the system locale rather than specifying a locale:

创建一个名为getTotal的函数。 此函数将使用范围内的变量total并返回一个本地化的字符串,您将使用该字符串显示合计。 使用undefined作为toLocaleString的第一个参数,以使用系统语言环境而不是指定语言环境:

hooks-tutorial/src/components/Product/Product.js
hooks-tutorial / src / components / Product / Product.js
import React, { useState } from 'react';
import './Product.css';

const currencyOptions = {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
}

export default function Product() {
  const [cart, setCart] = useState([]);
  const [total, setTotal] = useState(0);

  function getTotal() {
    return total.toLocaleString(undefined, currencyOptions)
  }

  return(
    <div className="wrapper">
      <div>
        Shopping Cart: {cart.length} total items.
      </div>
      <div>Total: {getTotal()}</div>

      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
      <button>Add</button> <button>Remove</button>
    </div>
  )
}

You now have added some string processing to the displayed total. Even though getTotal is a separate function, it shares the same scope as the surrounding function, which means it can reference the variables of the component.

现在,您已经在显示的总计中添加了一些字符串处理。 尽管getTotal是一个单独的函数,但它与周围的函数具有相同的作用域,这意味着它可以引用组件的变量。

Save the file. The page will reload and you’ll see the updated total with two decimal places:

保存文件。 该页面将重新加载,您将看到更新后的总数,保留两位小数:

This function works, but as of now, getTotal can only operate in this piece of code. In this case, you can convert it to a pure function, which gives the same outputs when given the same inputs and does not rely on a specific environment to operate. By converting the function to a pure function, you make it more reusable. You can, for example, extract it to a separate file and use it in multiple components.

该函数有效,但是getTotalgetTotal只能在这段代码中运行。 在这种情况下,您可以将其转换为纯函数 ,当给定相同的输入时,它会提供相同的输出,并且不依赖于特定的环境进行操作。 通过将函数转换为纯函数,可以使其更可重用。 例如,您可以将其提取到单独的文件中,并在多个组件中使用。

Update getTotal to take total as an argument. Then move the function outside of the component:

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

hooks-tutorial/src/components/Product/Product.js
hooks-tutorial / src / components / Product / Product.js
import React, { useState } from 'react';
import './Product.css';

const currencyOptions = {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
}

function getTotal(total) {
  return total.toLocaleString(undefined, currencyOptions)
}

export default function Product() {
  const [cart, setCart] = useState([]);
  const [total, setTotal] = useState(0);


  return(
    <div className="wrapper">
      <div>
        Shopping Cart: {cart.length} total items.
      </div>
      <div>Total: {getTotal(total)}</div><^>

      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
      <button>Add</button> <button>Remove</button>
    </div>
  )
}

Save the file. When you do, the page will reload and you’ll see the component as it was before.

保存文件。 完成后,页面将重新加载,您将看到组件之前的样子。

Functional components like this make it easier to move functions around. As long as there are no scope conflicts, you can move these conversion functions anywhere you want.

这样的功能组件使移动功能变得更加容易。 只要不存在作用域冲突,就可以将这些转换函数移至所需的任何位置。

In this step, you set the default value for a stateful piece of data using useState. You then saved the stateful data and a function for updating the state to variables using array destructuring. In the next step, you’ll use the update function to change the state value to re-render the page with updated information.

在此步骤中,您将使用useState为有状态数据设置默认值。 然后,您保存了有状态数据和使用数组解构将状态更新为变量的函数。 在下一步中,您将使用更新功能来更改状态值,以使用更新的信息重新呈现页面。

第2步-使用useState设置状态 (Step 2 — Setting State with useState)

In this step, you’ll update your product page by setting a new state with a static value. You have already created the function to update a piece of state, so now you’ll create an event to update both stateful variables with predefined values. By the end of this step, you’ll have a page with state that a user will be able to update at the click of a button.

在此步骤中,您将通过使用静态值设置新状态来更新产品页面。 您已经创建了更新状态的函数,因此现在您将创建一个事件,以使用预定义的值更新两个有状态变量。 到此步骤结束时,您将拥有一个页面,该页面的状态是用户单击按钮即可更新。

Unlike class-based components, you cannot update several pieces of state with a single function call. Instead, you must call each function individually. This means there is a greater separation of concerns, which helps keep stateful objects focused.

与基于类的组件不同,您不能通过单个函数调用来更新多个状态。 而是必须分别调用每个函数。 这意味着存在更大的关注点分离,这有助于使有状态对象保持焦点。

Create a function to add an item to the cart and update the total with the price of the item, then add that functionality to the Add button:

创建一个将商品添加到购物车并使用商品价格更新总计的函数,然后将该功能添加到“ 添加”按钮:

hooks-tutorial/src/components/Product/Product.js
hooks-tutorial / src / components / Product / Product.js
import React, { useState } from 'react';

...

export default function Product() {
  const [cart, setCart] = useState([]);
  const [total, setTotal] = useState(0);

  function add() {
    setCart(['ice cream']);
    setTotal(5);
  }

  return(
    <div className="wrapper">
      <div>
        Shopping Cart: {cart.length} total items.
      </div>
      <div>Total: {getTotal(total)}</div>

      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
      <button onClick={add}>Add</button><^>
      <button>Remove</button>
    </div>
  )
}

In this snippet, you called setCart with an array containing the word “ice cream” and called setTotal with 5. You then added this function to the onClick event handler for the Add button.

在该片段中,你称为setCart与含有字“冰淇淋”和称为阵列setTotal5 。 然后,您将此功能添加到了Add按钮的onClick事件处理程序中。

Notice that the function must have the same scope as the functions to set state, so it must be defined inside the component function.

请注意,该函数必须与设置状态的函数具有相同的作用域,因此必须在组件函数中定义它。

Save the file. When you do, the browser will reload, and when you click on the Add button the cart will update with the current amount:

保存文件。 完成后,浏览器将重新加载,并且当您单击“ 添加”按钮时,购物车将以当前金额更新:

Since you are not referencing a this context, you can use either an arrow function or a function declaration. They both work equally well here, and each developer or team can decide which style to use. You can even skip defining an extra function and pass the function directly into the onClick property.

由于未引用this上下文,因此可以使用箭头函数或函数声明。 他们在这里的表现均一样好,每个开发人员或团队都可以决定使用哪种样式。 您甚至可以跳过定义额外的功能,并将功能直接传递给onClick属性。

To try this out, create a function to remove the values by setting the cart to an empty object and the total to 0. Create the function in the onClick prop of the Remove button:

要尝试此操作,请创建一个函数来删除这些值,方法是将购物车设置为空对象,并将总计设置为0 。 在“ 删除”按钮的onClick道具中创建函数:

hooks-tutorial/src/component/Product/Product.js
hooks-tutorial / src / component / Product / Product.js
import React, { useState } from 'react';
...
export default function Product() {
  const [cart, setCart] = useState([]);
  const [total, setTotal] = useState(0);

  function add() {
    setCart(['ice cream']);
    setTotal(5);
  }

  return(
    <div className="wrapper">
      <div>
        Shopping Cart: {cart.length} total items.
      </div>
      <div>Total: {getTotal(total)}</div>

      <div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
      <button onClick={add}>Add</button>
      <button
        onClick={() => {
          setCart([]);
          setTotal(0);
        }}
      >
        Remove
      </button>
    </div>
  )
}

Save the file. When you do, you will be able to add and remove an item:

保存文件。 完成后,您将可以添加和删除项目:

Both strategies for assigning the function work, but there are some slight performance implications to creating an arrow function directly in a prop. In every re-render, React will create a new function, which would trigger a prop change and cause the component to re-render. When you define a function outside of a prop, you can take advantage of another Hook called useCallback. This will memoize the function, meaning that it will only create a new function if certain values change. If nothing changes, the program will use the cached memory of the function instead of recalculating it. Some components may not need that level of optimization, but as a rule, the higher a component is likely to be in a tree, the greater the need for memoization.

这两种分配函数的策略都可以工作,但是直接在prop中创建箭头函数会带来一些轻微的性能影响。 在每次重新渲染中,React都会创建一个新函数,这将触发属性更改并导致组件重新渲染。 在prop之外定义函数时,可以利用另一个名为useCallback Hook。 这将记住该函数,这意味着仅当某些值发生更改时,它才会创建一个新函数。 如果没有任何变化,程序将使用该函数的缓存内存,而不是重新计算它。 某些组件可能不需要那种优化级别,但是通常,树中的组件可能越高,对记忆的需求就越大。

In this step, you updated state data with functions created by the useState Hook. You created wrapping functions to call both functions to update the state of several pieces of data at the same time. But these functions are limited because they add static, pre-defined values instead of using the previous state to create the new state. In the next step, you’ll update the state using the current state with both the useState Hook and a new Hook called useReducer.

在此步骤中,您使用useState Hook创建的功能更新了状态数据。 您创建了包装函数来调用这两个函数以同时更新几条数据的状态。 但是这些功能受到限制,因为它们添加了静态的预定义值,而不是使用以前的状态来创建新状态。 在下一步中,您将使用useState Hook和一个名为useReducer的新Hook来使用当前状态更新状态。

步骤3 —使用当前状态设置状态 (Step 3 — Setting State Using Current State)

In the previous step, you updated state with a static value. It didn’t matter what was in the previous state—you always passed the same value. But a typical product page will have many items that you can add to a cart, and you’ll want to be able to update the cart while preserving the previous items.

在上一步中,您使用静态值更新了状态。 之前的状态没关系-您始终传递相同的值。 但是典型的产品页面会包含许多可以添加到购物车中的商品,并且您希望能够在保留先前商品的同时更新购物车。

In this step, you’ll update the state using the current state. You’ll expand your product page to include several products and you’ll create functions that update the cart and the total based on the current values. To update the values, you’ll use both the useState Hook and a new Hook called useReducer.

在此步骤中,您将使用当前状态更新状态。 您将扩展产品页面以包括多个产品,并创建基于当前值更新购物车和总数的函数。 要更新值,您将同时使用useState Hook和一个名为useReducer的新Hook。

Since React may optimize code by calling actions asynchronously, you’ll want to make sure that your function has access to the most up-to-date state. The most basic way to solve this problem is to pass a function to the state-setting function instead of a value. In other words, instead of calling setState(5), you’d call setState(previous => previous +5).

由于React可以通过异步调用动作来优化代码,因此您需要确保您的函数可以访问最新状态。 解决此问题的最基本方法是将一个函数而不是值传递给状态设置函数。 换句话说,不是调用setState(5) ,而是调用setState(previous => previous +5)

To start implementing this, add some more items to the product page by making a products array of objects, then remove the event handlers from the Add and Remove buttons to make room for the refactoring:

要开始实施此操作,请通过使对象products数组构成更多的项目到产品页面,然后从“ 添加”和“ 删除”按钮中删除事件处理程序,以便为重构腾出空间:

hooks-tutorial/src/component/Product/Product.js
hooks-tutorial / src / component / Product / Product.js
import React, { useState } from 'react';
import './Product.css';

...

const products = [
  {
    emoji: '🍦',
    name: 'ice cream',
    price: 5
  },
  {
    emoji: '🍩',
    name: 'donuts',
    price: 2.5,
  },
  {
    emoji: '🍉',
    name: 'watermelon',
    price: 4
  }
];

export default function Product() {
  const [cart, setCart] = useState([]);
  const [total, setTotal] = useState(0);

  function add() {
    setCart(['ice cream']);
    setTotal(5);
  }

  return(
    <div className="wrapper">
      <div>
        Shopping Cart: {cart.length} total items.
      </div>
      <div>Total: {getTotal(total)}</div>
        <div>
        {products.map(product => (
          <div key={product.name}>
            <div className="product">
              <span role="img" aria-label={product.name}>{product.emoji}</span>
            </div>
            <button>Add</button>
            <button>Remove</button>
          </div>
        ))}
      <^></div><^
    </div>
  )
}

You now have some JSX that uses the .map method to iterate over the array and display the products.

现在,您有了一些使用.map方法的 JSX,以遍历数组并显示产品。

Save the file. When you do, the page will reload and you’ll see multiple products:

保存文件。 完成后,页面将重新加载,您将看到多种产品:

Currently, the buttons have no actions. Since you only want to add the specific product on click, you’ll need to pass the product as an argument to the add function. In the add function, instead of passing the new item directly to the setCart and setTotal functions, you’ll pass an anonymous function that takes the current state and returns a new updated value:

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

hooks-tutorial/src/component/Product/Product.js
hooks-tutorial / src / component / Product / Product.js
import React, { useState } from 'react';
import './Product.css';
...
export default function Product() {
  const [cart, setCart] = useState([]);
  const [total, setTotal] = useState(0);

  function add(product) {
    setCart(current => [...current, product.name]);
    setTotal(current => current + product.price);
  }

  return(
    <div className="wrapper">
      <div>
        Shopping Cart: {cart.length} total items.
      </div>
      <div>Total: {getTotal(total)}</div>

      <div>
        {products.map(product => (
          <div key={product.name}>
            <div className="product">
              <span role="img" aria-label={product.name}>{product.emoji}</span>
            </div>
            <button onClick={() => add(product)}>Add</button>
            <button>Remove</button>
          </div>
        ))}
      </div>
    </div>
  )
}

The anonymous function uses the most recent state—either cart or total—as an argument that you can use to create a new value. Take care, though, not to directly mutate state. Instead, when adding a new value to the cart you can add the new product to the state by spreading the current value and adding the new value onto the end.

匿名函数使用最新状态( cart状态或total状态)作为可用于创建新值的参数。 但是要小心,不要直接改变状态。 相反,在向购物车中添加新值时,您可以通过分散当前值并将新值添加到末尾来将新产品添加到状态中。

Save the file. When you do, the browser will reload and you’ll be able to add multiple products:

保存文件。 完成后,浏览器将重新加载,您将能够添加多个产品:

There’s another Hook called useReducer that is specially designed to update the state based on the current state, in a manner similar to the .reduce array method. The useReducer Hook is similar to useState, but when you initialize the Hook, you pass in a function the Hook will run when you change the state along with the initial data. The function—referred to as the reducer—takes two arguments: the state and another argument. The other argument is what you will supply when you call the update function.

还有一个名为useReducer的Hook,它专门设计用于根据当前状态更新状态,类似于.reduce数组方法useReducer Hook与useState相似,但是在初始化Hook时,您传入一个函数,当您更改状态和初始数据时,该Hook将运行。 所述功能被称为reducer -takes两个参数:状态和另一种说法。 另一个参数是调用update函数时将提供的内容。

Refactor the cart state to use the useReducer Hook. Create a funciton called cartReducer that takes the state and the product as arguments. Replace useState with useReducer, then pass the cartReducer function as the first argument and an empty array as the second argument, which will be the initial data:

重构购物车状态以使用useReducer挂钩。 创建一个名为cartReducer ,该cartReducerstateproduct作为参数。 将useState替换为useReducer ,然后将cartReducer函数作为第一个参数,并将空数组作为第二个参数,这将是初始数据:

hooks-tutorial/src/component/Product/Product.js
hooks-tutorial / src / component / Product / Product.js
import React, { useReducer, useState } from 'react';

...

function cartReducer(state, product) {
  return [...state, product]
}

export default function Product() {
  const [cart, setCart] = useReducer(cartReducer, []);
  const [total, setTotal] = useState(0);

  function add(product) {
    setCart(product.name);
    setTotal(current => current + product.price);
  }

  return(
...
  )
}

Now when you call setCart, pass in the product name instead of a function. When you call setCart, you will call the reducer function, and the product will be the second argument. You can make a similar change with the total state.

现在,当您调用setCart ,传入产品名称而不是函数。 调用setCart ,将调用reducer函数,乘积将成为第二个参数。 您可以对total状态进行类似的更改。

Create a function called totalReducer that takes the current state and adds the new amount. Then replace useState with useReducer and pass the new value setCart instead of a function:

创建一个名为totalReducer的函数,该函数采用当前状态并添加新的金额。 然后更换useStateuseReducer ,并通过新的价值setCart而不是函数:

hooks-tutorial/src/component/Product/Product.js
hooks-tutorial / src / component / Product / Product.js
import React, { useReducer } from 'react';

...

function totalReducer(state, price) {
  return state + price;
}

export default function Product() {
  const [cart, setCart] = useReducer(cartReducer, []);
  const [total, setTotal] = useReducer(totalReducer, 0);

  function add(product) {
    setCart(product.name);
    setTotal(product.price);
  }

  return(
    ...
  )
}

Since you are no longer using the useState Hook, you removed it from the import.

由于不再使用useState Hook,因此将其从导入中删除。

Save the file. When you do, the page will reload and you’ll be able to add items to the cart:

保存文件。 完成后,页面将重新加载,您将可以在购物车中添加商品:

Now it’s time to add the remove function. But this leads to a problem: The reducer functions can handle adding items and updating totals, but it’s not clear how it will be able to handle removing items from the state. A common pattern in reducer functions is to pass an object as the second argument that contains the name of the action and the data for the action. Inside the reducer, you can then update the total based on the action. In this case, you will add items to the cart on an add action and remove them on a remove action.

现在是时候添加remove功能了。 但这会导致一个问题:reduce函数可以处理添加项和更新总计,但是尚不清楚如何处理从状态中删除项。 reducer函数中的一个常见模式是将一个对象作为第二个参数传递,该对象包含操作的名称和该操作的数据。 在减速器内部,然后可以根据操作更新总数。 在这种情况下,您将通过add操作将商品添加到购物车,并通过remove操作将其remove

Start with the totalReducer. Update the function to take an action as the second argument, then add a conditional to update the state based on the action.type:

totalReducer开始。 更新函数以将action作为第二个参数,然后添加一个条件以根据action.type更新状态:

hooks-tutorial/src/component/Product/Product.js
hooks-tutorial / src / component / Product / Product.js
import React, { useReducer } from 'react';
import './Product.css';

...

function totalReducer(state, action) {
  if(action.type === 'add') {
    return state + action.price;
  }
  return state - action.price
}

export default function Product() {
  const [cart, setCart] = useReducer(cartReducer, []);
  const [total, setTotal] = useReducer(totalReducer, 0);

  function add(product) {
    const { name, price } = product;
    setCart(name);
    setTotal({ price, type: 'add' });
  }

  return(
    ...
  )
}

The action is an object with two properites: type and price. The type can be either add or remove, and the price is a number. If the type is add, it increases the total. If it is remove, it lowers the total. After updating the totalReducer, you call setTotal with a type of add and the price, which you set using destructuring assignment.

action是具有两个属性的对象: typeprice 。 类型可以是addremoveprice是一个数字。 如果类型为add ,则会增加总数。 如果将其remove ,则会降低总数。 更新totalReducer ,您将使用add typeprice调用setTotal ,这些type是使用解构分配设置的。

Next, you will update the cartReducer. This one is a little more complicated: You can use if/then conditionals, but it’s more common to use a switch statement. Switch statements are particularly useful if you have a reducer that can handle many different actions because it makes those actions more readable in your code.

接下来,您将更新cartReducer 。 这有点复杂:您可以使用if/then条件语句 ,但更常见的是使用switch语句 。 如果您拥有一个可以处理许多不同动作的化简器,则switch语句特别有用,因为它使这些动作在代码中更具可读性。

As with the totalReducer, you’ll pass an object as the second item type and name properties. If the action is remove, update the state by splicing out the first instance of a product.

totalReducer ,您将传递一个对象作为第二项typename属性。 如果操作为remove ,则通过拼接产品的第一个实例来更新状态。

After updating the cartReducer, create a remove function that calls setCart and setTotal with objects containing type: 'remove' and either the price or the name. Then use a switch statement to update the data based on the action type. Be sure to return the final state:

更新cartReducer ,创建一个带有以下type: 'remove'对象的remove函数,该函数调用setCartsetTotal type: 'remove'以及pricename 。 然后使用switch语句根据操作类型更新数据。 确保返回最终状态:

hooks-tutorial/src/complicated/Product/Product.js
hooks-tutorial / src / complicated / Product / Product.js
import React, { useReducer } from 'react';
import './Product.css';

...

function cartReducer(state, action) {
  switch(action.type) {
    case 'add':
      return [...state, action.name];
    case 'remove':
      const update = [...state];
      update.splice(update.indexOf(action.name), 1);
      return update;
    default:
      return state;
  }
}

function totalReducer(state, action) {
  if(action.type === 'add') {
    return state + action.price;
  }
  return state - action.price
}

export default function Product() {
  const [cart, setCart] = useReducer(cartReducer, []);
  const [total, setTotal] = useReducer(totalReducer, 0);

  function add(product) {
    const { name, price } = product;
    setCart({ name, type: 'add' });
    setTotal({ price, type: 'add' });
  }

  function remove(product) {
    const { name, price } = product;
    setCart({ name, type: 'remove' });
    setTotal({ price, type: 'remove' });
  }

  return(
    <div className="wrapper">
      <div>
        Shopping Cart: {cart.length} total items.
      </div>
      <div>Total: {getTotal(total)}</div>

      <div>
        {products.map(product => (
          <div key={product.name}>
            <div className="product">
              <span role="img" aria-label={product.name}>{product.emoji}</span>
            </div>
            <button onClick={() => add(product)}>Add</button>
            <button onClick={() => remove(product)}>Remove</button>
          </div>
        ))}
      </div>
    </div>
  )
}

As you work on your code, take care not to directly mutate the state in the reducer functions. Instead, make a copy before splicing out the object. Also note it is a best practice to add a default action on a switch statement in order to account for unforeseen edge cases. In this, case just return the object. Other options for the default are throwing an error or falling back to an action such as add or remove.

在处理代码时,请注意不要直接改变化简函数中的状态。 相反,请在splicing对象之前进行复制。 还要注意,最好的做法是在switch语句上添加default操作,以解决无法预料的边缘情况。 在这种情况下,只需返回对象即可。 default其他选项是引发错误或退回诸如添加或删除之类的操作。

After making the changes, save the file. When the browser refreshes, you’ll be able to add and remove items:

进行更改后,保存文件。 浏览器刷新后,您可以添加和删除项目:

There is still a subtle bug left in this product. In the remove method, you can subtract from a price even if the item is not in the cart. If you click Remove on the ice cream without adding it to your cart, your displayed total will be -5.00.

该产品中仍然存在一个细微的错误。 在remove方法中,即使商品不在购物车中,您也可以从价格中扣除。 如果您在不将冰淇淋添加到购物车的情况下单击“ 删除” ,则显示的总计为-5.00

You can fix this bug by checking that an item exists before you subtract it, but a more efficient way is to minimize the different pieces of state by only saving related data in one place. In other words, try to avoid double references to the same data, in this case, the product. Instead, store the raw data in one state variable—the whole product object—then perform the calculations using that data.

您可以通过在减去项目之前检查项目是否存在来解决此错误,但是一种更有效的方法是通过仅将相关数据保存在一个位置来最小化不同的状态。 换句话说,尝试避免对同一数据(在这种情况下为乘积)的双重引用。 而是将原始数据存储在一个状态变量(整个产品对象)中,然后使用该数据执行计算。

Refactor the component so that the add() function passes the whole product to the reducer and the remove() function removes the whole object. The getTotal method will use the cart, and so you can delete the totalReducer function. Then you can pass the cart to getTotal(), which you can refactor to reduce the array to a single value:

重构组件,以便add()函数将整个乘积传递给reducer,而remove()函数将整个对象移除。 getTotal方法将使用购物车,因此您可以删除totalReducer函数。 然后,您可以将购物车传递给getTotal() ,您可以对其进行重构以将数组减少为单个值:

hooks-tutorial/src/component/Product/Product.js
hooks-tutorial / src / component / Product / Product.js
import React, { useReducer } from 'react';
import './Product.css';

const currencyOptions = {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
}

function getTotal(cart) {
  const total = cart.reduce((totalCost, item) => totalCost + item.price, 0);
  return total.toLocaleString(undefined, currencyOptions)
}

...

function cartReducer(state, action) {
  switch(action.type) {
    case 'add':
      return [...state, action.product];
    case 'remove':
      const productIndex = state.findIndex(item => item.name === action.product.name);
      if(productIndex < 0) {
        return state;
      }
      const update = [...state];
      update.splice(productIndex, 1)
      return update
    default:
      return state;
  }
}

export default function Product() {
  const [cart, setCart] = useReducer(cartReducer, []);

  function add(product) {
    setCart({ product, type: 'add' });
  }

  function remove(product) {
    setCart({ product, type: 'remove' });
  } 

  return(
    <div className="wrapper">
      <div>
        Shopping Cart: {cart.length} total items.
      </div>
      <div>Total: {getTotal(cart)}</div>

      <div>
        {products.map(product => (
          <div key={product.name}>
            <div className="product">
              <span role="img" aria-label={product.name}>{product.emoji}</span>
            </div>
            <button onClick={() => add(product)}>Add</button>
            <button onClick={() => remove(product)}>Remove</button>
          </div>
        ))}
      </div>
    </div>
  )
}

Save the file. When you do, the browser will refresh and you’ll have your final cart:

保存文件。 完成后,浏览器将刷新,您将获得最终的购物车:

By using the useReducer Hook, you kept your main component body well-organized and legible, since the complex logic for parsing and splicing the array is outside of the component. You also could move the reducer outside the componet if you wanted to reuse it, or you can create a custom Hook to use across multiple components. You can make custom Hooks as functions surrounding basic Hooks, such as useState, useReducer, or useEffect.

通过使用useReducer Hook,您可以使主要组件的主体井井有条,清晰易读,因为解析和拼接数组的复杂逻辑不在组件之外。 如果要重用减速器,也可以将其移动到组件之外,或者可以创建自定义的挂钩以在多个组件中使用。 您可以将自定义挂钩作为基本挂钩周围的函数,例如useStateuseReduceruseEffect

Hooks give you the chance to move the stateful logic in and out of the component, as opposed to classes, where you are generally bound to the component. This advantage can extend to other components as well. Since Hooks are functions, you can import them into multiple components rather then using inheritance or other complex forms of class composition.

钩子使您有机会将有状态逻辑移入和移出组件,这与类通常与组件绑定的类相反。 该优点也可以扩展到其他组件。 由于Hook是函数,因此可以将它们导入到多个组件中,而不必使用继承或其他复杂的类组合形式。

In this step, you learned to set state using the current state. You created a component that updated state using both the useState and the useReducer Hooks, and you refactored the component to different Hooks to prevent bugs and improve reusability.

在这一步中,您学习了使用当前状态来设置状态。 您创建了一个同时使用useStateuseReducer Hooks更新状态的组件,并将该组件重构为不同的Hooks,以防止错误并提高可重用性。

结论 (Conclusion)

Hooks were a major change to React that created a new way to share logic and update components without using classes. Now that you can create components using useState and useReducer, you have the tools to make complex projects that respond to users and dynamic information. You also have a foundation of knowledge that you can use to explore more complex Hooks or to create custom Hooks.

钩子是对React的重大更改,它创建了一种无需使用类即可共享逻辑和更新组件的新方法。 现在您可以使用useStateuseReducer创建组件,您已经拥有了用于创建响应用户和动态信息的复杂项目的工具。 您还具有知识基础,可用于探索更复杂的挂钩或创建自定义挂钩。

If you would like to look at more React tutorials, check out our React Topic page, or return to the How To Code in React.js series page.

如果您想查看更多React教程,请查看我们的React Topic页面 ,或者返回到React.js系列中How To Code页面

翻译自: https://www.digitalocean.com/community/tutorials/how-to-manage-state-with-hooks-on-react-components

react hooks使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值