react hooks使用
The author selected Creative Commons to receive a donation as part of the Write for DOnations program.
作者选择了创用CC来接受捐赠,这是Write for DOnations计划的一部分。
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
.
在本教程中,您将学习如何使用useState
和useReducer
Hooks设置状态。 当设置一个值而不引用当前状态时, useState
Hook很有用。 当您需要引用先前的值或执行不同的操作需要复杂的数据操作时, useReducer
挂钩非常有用。 为了探索这些不同的状态设置方法,您将创建一个带有购物车的产品页面组件,然后通过从选项列表中添加购买来更新购物车。 在本教程结束时,您将习惯于使用Hooks在功能组件中管理状态,并且为更高级的Hook(例如useEffect
, useMemo
和useContext
奠定了基础。
You will need a development environment running Node.js; this tutorial was tested on Node.js version 10.20.1 and npm version 6.14.4. To install this on macOS or Ubuntu 18.04, follow the steps in How to Install Node.js and Create a Local Development Environment on macOS or the Installing Using a PPA section of How To Install Node.js on Ubuntu 18.04.
您将需要一个运行Node.js的开发环境; 本教程已在Node.js 10.20.1版和npm 6.14.4版上进行了测试。 要将其安装在macOS或Ubuntu 18.04上,请遵循如何在macOS上安装Node.js并创建本地开发环境中的步骤,或如何在Ubuntu 18.04上安装Node.js的 使用PPA安装部分中的步骤 。
A React development environment set up with Create React App, with the non-essential boilerplate removed. To set this up, follow Step 1 — Creating an Empty Project of the How To Manage State on React Class Components tutorial. This tutorial will use hooks-tutorial
as the project name.
使用Create React App设置的React开发环境,删除了不必要的样板。 要进行设置,请遵循“如何在React类组件上管理状态”教程的步骤1-创建一个空项目 。 本教程将使用hooks-tutorial
作为项目名称。
You will also need a basic knowledge of JavaScript, which you can find in How To Code in JavaScript, along with a basic knowledge of HTML and CSS. A useful resource for HTML and CSS is the Mozilla Developer Network.
您还将需要JavaScript的基本知识,以及HTML和CSS的基本知识,这些知识可以在JavaScript的How To Code中找到。 Mozilla开发人员网络是HTML和CSS的有用资源。
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
组件创建目录:
Next, open up a file called Product.js
in the Product
directory:
接下来,在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:
将以下代码添加到文件中:
import React from 'react';
import './Product.css';
export default function Product() {
return(
Shopping Cart: 0 total items.
Total: 0
)
}
In this code, you used JSX to create the HTML elements for the 在此代码中,您使用JSX创建了 Save and close the file, then create a new file called 保存并关闭文件,然后在 Add some styling to increase the font size for the text and the emoji: 添加一些样式以增加文本和表情符号的字体大小: The emoji will need a much larger 表情符号将需要更大的 Save and close the file. Now, add the component into the 保存并关闭文件。 现在,将组件添加到 Import the component and render it. Also, delete the CSS import since you won’t be using it in this tutorial: 导入组件并渲染它。 另外,删除CSS导入,因为在本教程中将不使用它: Save and close the file. When you do, the browser will refresh and you’ll see the 保存并关闭文件。 完成后,浏览器将刷新,您将看到“ 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导出了几个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 挂钩是使您能够在React生命周期中运行操作的函数。 挂钩是由其他动作或组件属性的更改触发的,用于创建数据或触发进一步的更改。 例如, 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 要尝试此操作,请打开 Next, import the 接下来,通过添加突出显示的代码从React导入 Create your first Hook by invoking the 通过使用一个空数组调用 Here you assigned the first value, the state, to a variable called 在这里,您将第一个值(状态)分配给了一个名为 In addition to the 除了 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 Hooks与基于类的状态管理之间的一个重要区别是,在基于类的状态管理中,只有一个状态对象。 使用Hook,状态对象彼此完全独立,因此您可以根据需要拥有任意数量的状态对象。 这意味着,如果您想要一块新的有状态数据,您要做的就是用新的默认值调用 Inside 在 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 现在,您已经有了一些状态数据,您可以标准化显示的数据,以提供更可预测的体验。 例如,由于此示例中的总计是价格,因此它将始终有两个小数位。 您可以使用 Create a function called 创建一个名为 You now have added some string processing to the displayed total. Even though 现在,您已经在显示的总计中添加了一些字符串处理。 尽管 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, 该函数有效,但是 Update 更新 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 在此步骤中,您将使用 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: 创建一个将商品添加到购物车并使用商品价格更新总计的函数,然后将该功能添加到“ 添加”按钮: In this snippet, you called 在该片段中,你称为 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 由于未引用 To try this out, create a function to remove the values by setting the cart to an empty object and the total to 要尝试此操作,请创建一个函数来删除这些值,方法是将购物车设置为空对象,并将总计设置为 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 这两种分配函数的策略都可以工作,但是直接在prop中创建箭头函数会带来一些轻微的性能影响。 在每次重新渲染中,React都会创建一个新函数,这将触发属性更改并导致组件重新渲染。 在prop之外定义函数时,可以利用另一个名为 In this step, you updated state data with functions created by the 在此步骤中,您使用 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 在此步骤中,您将使用当前状态更新状态。 您将扩展产品页面以包括多个产品,并创建基于当前值更新购物车和总数的函数。 要更新值,您将同时使用 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 由于React可以通过异步调用动作来优化代码,因此您需要确保您的函数可以访问最新状态。 解决此问题的最基本方法是将一个函数而不是值传递给状态设置函数。 换句话说,不是调用 To start implementing this, add some more items to the product page by making a 要开始实施此操作,请通过使对象的 You now have some JSX that uses the 现在,您有了一些使用 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 当前,按钮没有动作。 由于您只想在点击时添加特定产品,因此您需要将该产品作为参数传递给 The anonymous function uses the most recent state—either 匿名函数使用最新状态( Save the file. When you do, the browser will reload and you’ll be able to add multiple products: 保存文件。 完成后,浏览器将重新加载,您将能够添加多个产品: There’s another Hook called 还有一个名为 Refactor the cart state to use the 重构购物车状态以使用 Now when you call 现在,当您调用 Create a function called 创建一个名为 Since you are no longer using the 由于不再使用 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 现在是时候添加 Start with the 从 The Next, you will update the 接下来,您将更新 As with the 与 After updating the 更新 As you work on your code, take care not to directly mutate the state in the reducer functions. Instead, make a copy before 在处理代码时,请注意不要直接改变化简函数中的状态。 相反,请在 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 该产品中仍然存在一个细微的错误。 在 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 重构组件,以便 Save the file. When you do, the browser will refresh and you’ll have your final cart: 保存文件。 完成后,浏览器将刷新,您将获得最终的购物车: By using the 通过使用 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 在这一步中,您学习了使用当前状态来设置状态。 您创建了一个同时使用 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 钩子是对React的重大更改,它创建了一种无需使用类即可共享逻辑和更新组件的新方法。 现在您可以使用 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使用Product
component, with an ice cream emoji to represent the product. In addition, two of the Product
组件HTML元素,并用冰淇淋表情符号代表了产品。 另外,两个Product.css
in the Product
directory: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;
}
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
来删除按钮上的默认渐变背景。 App
component to render the Product
component in the browser. Open App.js
:App
组件中以在浏览器中呈现Product
组件。 打开App.js
:
import React from 'react';
import Product from '../Product/Product';
function App() {
return
Product
component:Product
组件: 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
包中导入它们。 按照约定,React Hooks以单词use
开头,例如useState
, useContext
和useReducer
。 大多数第三方库都遵循相同的约定。 例如, Redux具有useSelector
和useStore
Hook 。 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.useState
Hook生成一个有状态的数据以及用于更改该数据并触发重新渲染的函数。 它将创建动态代码段,并在数据更改时触发重新渲染,从而进入生命周期。 实际上,这意味着您可以使用useState
Hook将动态数据存储在变量中。 Product.js
:Product.js
:
useState
Hook from React by adding the highlighted code:useState
Hook: import React, { useState } from 'react';
import './Product.css';
export default function Product() {
return(
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
,而不必担心名称冲突,因为您可以将每一个状态分配给更新的函数,并将函数更新为明确命名的变量。 useState
Hook with an empty array. Add in the following highlighted code: useState
Hook来创建您的第一个Hook。 添加以下突出显示的代码: import React, { useState } from 'react';
import './Product.css';
export default function Product() {
const [cart, setCart] = useState([]);
return(
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
。 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
状态。 useState
with a new default and assign the result to new variables.useState
并将结果分配给新变量。 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
并将值和函数分配给total
和setTotal
: import React, { useState } from 'react';
import './Product.css';
export default function Product() {
const [cart, setCart] = useState([]);
const [total, setTotal] = useState(0);
return(
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
从数字转换为两位小数的字符串。 还将根据与浏览器区域设置匹配的数字约定将数字转换为字符串。 您可以设置选项minimumFractionDigits
和maximumFractionDigits
给小数的数一致。 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
的第一个参数,以使用系统语言环境而不是指定语言环境: 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(
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
是一个单独的函数,但它与周围的函数具有相同的作用域,这意味着它可以引用组件的变量。 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.getTotal
, getTotal
只能在这段代码中运行。 在这种情况下,您可以将其转换为纯函数 ,当给定相同的输入时,它会提供相同的输出,并且不依赖于特定的环境进行操作。 通过将函数转换为纯函数,可以使其更可重用。 例如,您可以将其提取到单独的文件中,并在多个组件中使用。 getTotal
to take total
as an argument. Then move the function outside of the component:getTotal
以将total
作为参数。 然后将函数移到组件之外: 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(
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
)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(
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
与含有字“冰淇淋”和称为阵列setTotal
与5
。 然后,您将此功能添加到了Add按钮的onClick
事件处理程序中。 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
属性。 0
. Create the function in the onClick
prop of the Remove button:0
。 在“ 删除”按钮的onClick
道具中创建函数: 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(
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.useCallback
Hook。 这将记住该函数,这意味着仅当某些值发生更改时,它才会创建一个新函数。 如果没有任何变化,程序将使用该函数的缓存内存,而不是重新计算它。 某些组件可能不需要那种优化级别,但是通常,树中的组件可能越高,对记忆的需求就越大。 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)
useState
Hook and a new Hook called useReducer
. useState
Hook和一个名为useReducer
的新Hook。 setState(5)
, you’d call setState(previous => previous +5)
.setState(5)
,而是调用setState(previous => previous +5)
。 products
array of objects, then remove the event handlers from the Add and Remove buttons to make room for the refactoring:products
数组构成更多的项目到产品页面,然后从“ 添加”和“ 删除”按钮中删除事件处理程序,以便为重构腾出空间: 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(
.map
method to iterate over the array and display the products..map
方法的 JSX,以遍历数组并显示产品。 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
函数中,您将传递一个采用当前状态并返回新的更新值的匿名函数,而不是将新项直接传递给setCart
和setTotal
函数: 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(
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
状态)作为可用于创建新值的参数。 但是要小心,不要直接改变状态。 相反,在向购物车中添加新值时,您可以通过分散当前值并将新值添加到末尾来将新产品添加到状态中。 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函数时将提供的内容。 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
,该cartReducer
将state
和product
作为参数。 将useState
替换为useReducer
,然后将cartReducer
函数作为第一个参数,并将空数组作为第二个参数,这将是初始数据: 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(
...
)
}
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
状态进行类似的更改。 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
的函数,该函数采用当前状态并添加新的金额。 然后更换useState
与useReducer
,并通过新的价值setCart
而不是函数: 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(
...
)
}
useState
Hook, you removed it from the import.useState
Hook,因此将其从导入中删除。 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
。 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
更新状态: 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(
...
)
}
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
是具有两个属性的对象: type
和price
。 类型可以是add
或remove
, price
是一个数字。 如果类型为add
,则会增加总数。 如果将其remove
,则会降低总数。 更新totalReducer
,您将使用add
type
和price
调用setTotal
,这些type
是使用解构分配设置的。 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语句特别有用,因为它使这些动作在代码中更具可读性。 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
,您将传递一个对象作为第二项type
和name
属性。 如果操作为remove
,则通过拼接产品的第一个实例来更新状态。 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
函数,该函数调用setCart
和setTotal
type: 'remove'
以及price
或name
。 然后使用switch语句根据操作类型更新数据。 确保返回最终状态: 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(
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
其他选项是引发错误或退回诸如添加或删除之类的操作。 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 。 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()
,您可以对其进行重构以将数组减少为单个值: 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(
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,您可以使主要组件的主体井井有条,清晰易读,因为解析和拼接数组的复杂逻辑不在组件之外。 如果要重用减速器,也可以将其移动到组件之外,或者可以创建自定义的挂钩以在多个组件中使用。 您可以将自定义挂钩作为基本挂钩周围的函数,例如useState
, useReducer
或useEffect
。 useState
and the useReducer
Hooks, and you refactored the component to different Hooks to prevent bugs and improve reusability.useState
和useReducer
Hooks更新状态的组件,并将该组件重构为不同的Hooks,以防止错误并提高可重用性。 结论 (Conclusion)
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.useState
和useReducer
创建组件,您已经拥有了用于创建响应用户和动态信息的复杂项目的工具。 您还具有知识基础,可用于探索更复杂的挂钩或创建自定义挂钩。