react无状态组件
The author selected Creative Commons to receive a donation as part of the Write for DOnations program.
作者选择了创用CC来接受捐赠,这是Write for DOnations计划的一部分。
介绍 (Introduction)
In React, state refers to a structure that keeps track of how data changes over time in your application. Managing state is a crucial skill in React because it allows you to make interactive components and dynamic web applications. State is used for everything from tracking form inputs to capturing dynamic data from an API. In this tutorial, you’ll run through an example of managing state on class-based components.
在React中 , 状态是指跟踪应用程序中数据随时间变化的结构。 管理状态是React中的一项关键技能,因为它允许您制作交互式组件和动态Web应用程序。 状态可用于从跟踪表单输入到从API捕获动态数据的所有内容。 在本教程中,您将通过一个示例管理基于类的组件上的状态 。
As of the writing of this tutorial, the official React documentation encourages developers to adopt React Hooks to manage state with functional components when writing new code, rather than using class-based components. Although the use of React Hooks is considered a more modern practice, it’s important to understand how to manage state on class-based components as well. Learning the concepts behind state management will help you navigate and troubleshoot class-based state management in existing code bases and help you decide when class-based state management is more appropriate. There’s also a class-based method called componentDidCatch
that is not available in Hooks and will require setting state using class methods.
在撰写本教程时,官方的React文档鼓励开发人员在编写新代码时采用React Hooks来管理具有功能组件的状态,而不是使用基于类的组件 。 尽管使用React Hooks被认为是更现代的做法,但是了解如何在基于类的组件上管理状态也很重要。 学习状态管理背后的概念将有助于您在现有代码库中导航和解决基于类的状态管理,并帮助您确定何时更适合基于类的状态管理。 还有一个名为componentDidCatch
的基于类的方法,该方法在Hooks中不可用,并且需要使用类方法设置状态。
This tutorial will first show you how to set state using a static value, which is useful for cases where the next state does not depend on the first state, such as setting data from an API that overrides old values. Then it will run through how to set a state as the current state, which is useful when the next state depends on the current state, such as toggling a value. To explore these different ways of setting state, you’ll create a product page component that you’ll update by adding purchases from a list of options.
本教程将首先向您展示如何使用静态值设置状态,这对于下一个状态不依赖于第一个状态的情况很有用,例如从API覆盖旧值的情况下设置数据。 然后它将遍历如何将一个状态设置为当前状态,这在下一个状态取决于当前状态(例如切换值)时非常有用。 为了探索这些不同的状态设置方法,您将创建一个产品页面组件,并通过从选项列表中添加购买来更新该页面。
先决条件 (Prerequisites)
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安装部分中的步骤 。
In this tutorial, you will create apps with Create React App. You can find instructions for installing an application with Create React App at How To Set Up a React Project with Create React App.
在本教程中,您将使用Create React App创建应用程序 。 您可以在如何使用创建React App 设置React项目中找到有关使用Create React App安装应用程序的说明。
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 good resource for HTML and CSS is the Mozilla Developer Network.
您还将需要JavaScript的基本知识,以及HTML和CSS的基本知识,这些知识可以在JavaScript的How To Code中找到。 Mozilla开发人员网络是HTML和CSS的良好资源。
第1步-创建一个空项目 (Step 1 — Creating an Empty Project)
In this step, you’ll create a new project using Create React App. Then you will delete the sample project and related files that are installed when you bootstrap the project. Finally, you will create a simple file structure to organize your components. This will give you a solid basis on which to build this tutorial’s sample application for managing state on class-based components.
在这一步中,您将使用Create React App创建一个新项目。 然后,您将删除示例项目以及在引导项目时安装的相关文件。 最后,您将创建一个简单的文件结构来组织您的组件。 这将为您构建本教程的示例应用程序打下坚实的基础,以管理基于类的组件上的状态。
To start, make a new project. In your terminal, run the following script to install a fresh project using create-react-app
:
首先,创建一个新项目。 在您的终端中,运行以下脚本以使用create-react-app
安装新项目:
npx create-react-app state-class-tutorial
npx create-react-app 状态类教程
After the project is finished, change into the directory:
项目完成后,转到目录:
cd state-class-tutorial
cd state-class-tutorial
In a new terminal tab or window, start the project using the Create React App start script. The browser will auto-refresh on changes, so leave this script running while you work:
在新的终端标签或窗口中,使用Create React App启动脚本启动项目。 浏览器将自动刷新所做的更改,因此在工作时请保持此脚本运行:
- npm start npm开始
You will get a running local server. If the project did not open in a browser window, you can open it with http://localhost:3000/
. If you are running this from a remote server, the address will be http://your_domain:3000
.
您将获得正在运行的本地服务器。 如果未在浏览器窗口中打开项目,则可以使用http://localhost:3000/
打开它。 如果您是从远程服务器运行的,则该地址将为http:// your_domain :3000
。
Your browser will load with a simple React application included as part of Create React App:
您的浏览器将加载一个简单的React应用程序,该应用程序是Create React App的一部分:
You will be building a completely new set of custom components, so you’ll need to start by clearing out some boilerplate code so that you can have an empty project.
您将构建一套全新的自定义组件,因此您需要从清除一些样板代码开始,以便拥有一个空项目。
To start, open src/App.js
in a text editor. This is the root component that is injected into the page. All components will start from here. You can find more information about App.js
at How To Set Up a React Project with Create React App.
首先,在文本编辑器中打开src/App.js
这是注入页面的根组件。 所有组件将从此处开始。 您可以在如何使用创建React App设置React项目中找到有关App.js
更多信息。
Open src/App.js
with the following command:
使用以下命令打开src/App.js
:
- nano src/App.js 纳米src / App.js
You will see a file like this:
您将看到如下文件:
import React from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
Delete the line import logo from './logo.svg';
. Then replace everything in the return
statement to return a set of empty tags: <></>
. This will give you a valid page that returns nothing. The final code will look like this:
import logo from './logo.svg';
删除行import logo from './logo.svg';
。 然后替换return
语句中的所有内容以返回一组空标签: <></>
。 这将为您提供一个不返回任何内容的有效页面。 最终代码如下所示:
import React from 'react';
import './App.css';
function App() {
return <></>;
}
export default App;
Save and exit the text editor.
保存并退出文本编辑器。
Finally, delete the logo. You won’t be using it in your application and you should remove unused files as you work. It will save you from confusion in the long run.
最后,删除徽标。 您将不会在应用程序中使用它,并且应在工作时删除未使用的文件。 从长远来看,它将使您避免混乱。
In the terminal window type the following command:
在终端窗口中,输入以下命令:
- rm src/logo.svg rm src / logo.svg
If you look at your browser, you will see a blank screen.
如果您查看浏览器,将会看到一个空白屏幕。
Now that you have cleared out the sample Create React App project, create a simple file structure. This will help you keep your components isolated and independent.
现在,您已经清除了示例创建React App项目,创建一个简单的文件结构。 这将帮助您使组件保持隔离和独立。
Create a directory called components
in the src
directory. This will hold all of your custom components.
在src
目录中创建一个名为components
的目录。 这将保存您所有的自定义组件。
- mkdir src/components mkdir src /组件
Each component will have its own directory to store the component file along with the styles, images, and tests.
每个组件都有自己的目录来存储组件文件以及样式,图像和测试。
Create a directory for App
:
为App
创建目录:
- mkdir src/components/App mkdir src / components / App
Move all of the App
files into that directory. Use the wildcard, *
, to select any files that start with App.
regardless of file extension. Then use the mv
command to put them into the new directory:
将所有App
文件移到该目录。 使用通配符*
来选择以App.
开头的任何文件App.
无论文件扩展名如何。 然后使用mv
命令将它们放入新目录:
- mv src/App.* src/components/App mv src / App。* src / components / App
Next, update the relative import path in index.js
, which is the root component that bootstraps the whole process:
接下来,更新index.js
的相对导入路径,它是引导整个过程的根组件:
- nano src/index.js 纳米src / index.js
The import statement needs to point to the App.js
file in the App
directory, so make the following highlighted change:
import语句需要指向App
目录中的App.js
文件,因此请进行以下突出显示的更改:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App/App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
Save and exit the file.
保存并退出文件。
Now that the project is set up, you can create your first component.
现在已经建立了项目,您可以创建第一个组件。
步骤2 —在组件中使用状态 (Step 2 — Using State in a Component)
In this step, you’ll set the initial state of a component on its class and reference the state to display a value. You’ll then make a product page with a shopping cart that displays the total items in the cart using the state value. By the end of the step, you’ll know the different ways to hold a value and when you should use state rather than a prop or a static value.
在此步骤中,将在其类上设置组件的初始状态,并引用该状态以显示值。 然后,您将制作一个包含购物车的产品页面,该购物车页面将使用状态值显示购物车中的全部商品。 在该步骤结束时,您将了解保存值的不同方法,以及何时应使用状态而不是prop或静态值。
构建组件 (Building the Components)
Start by creating a directory for Product
:
首先为Product
创建一个目录:
- mkdir src/components/Product mkdir src /组件/产品
Next, open up Product.js
in that directory:
接下来,在该目录中打开Product.js
:
- nano src/components/Product/Product.js 纳米src / components / Product / Product.js
Start by creating a component with no state. The component will have two parts: The cart, which has the number of items and the total price, and the product, which has a button to add and remove an item. For now, the buttons will have no actions.
首先创建一个没有状态的组件。 该组件将包括两个部分:购物车(具有项目数和总价)以及产品(具有按钮)以添加和删除项目。 目前,这些按钮将不起作用。
Add the following code to Product.js
:
将以下代码添加到Product.js
:
import React, { Component } from 'react';
import './Product.css';
export default class Product extends Component {
render() {
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>
)
}
}
You have also included a couple of div
elements that have JSX class names so you can add some basic styling.
您还包括了几个具有JSX类名称的div
元素,因此您可以添加一些基本样式。
Save and close the file, then open Product.css
:
保存并关闭文件,然后打开Product.css
:
- nano src/components/Product/Product.css 纳米src / components / Product / Product.css
Give some light styling to increase the font-size
for the text and the emoji:
进行一些简单的样式化,以增加文本和表情符号的font-size
:
.product span {
font-size: 100px;
}
.wrapper {
padding: 20px;
font-size: 20px;
}
.wrapper button {
font-size: 20px;
background: none;
}
The emoji will need a much larger font size than the text, since it’s acting as the product image in this example. In addition, you are removing the default gradient background on buttons by setting the background
to none
.
表情符号需要比文本大得多的字体大小,因为在此示例中,表情符号充当产品图像。 此外,您可以通过将background
设置为none
来删除按钮上的默认渐变背景。
Save and close the file.
保存并关闭文件。
Now, render the Product
component in the App
component so you can see the results 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. You can also delete the CSS import since you won’t be using it in this tutorial:
导入组件并渲染它。 您也可以删除CSS导入,因为在本教程中将不使用它:
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
组件。
在类组件上设置初始状态 (Setting the Initial State on a Class Component)
There are two values in your component values that are going to change in your display: total number of items and total cost. Instead of hard coding them, in this step you’ll move them into an object called state
.
组件值中有两个值将在显示中更改:项目总数和总成本。 无需对它们进行硬编码,在这一步中,您将它们移动到一个名为state
的对象中 。
The state
of a React class is a special property that controls the rendering of a page. When you change the state, React knows that the component is out-of-date and will automatically re-render. When a component re-renders, it modifies the rendered output to include the most up-to-date information in state
. In this example, the component will re-render whenever you add a product to the cart or remove it from the cart. You can add other properties to a React class, but they won’t have the same ability to trigger re-rendering.
React类的state
是一个特殊属性,用于控制页面的呈现。 当您更改状态时,React会知道该组件已过期,并将自动重新渲染。 重新渲染组件时,它将修改渲染的输出以包括state
的最新信息。 在此示例中,每当您将产品添加到购物车或从购物车中删除产品时,组件都会重新渲染。 您可以将其他属性添加到React类,但是它们将不具备触发重新渲染的功能。
Open Product.js
:
打开Product.js
:
- nano src/components/Product/Product.js 纳米src / components / Product / Product.js
Add a property called state
to the Product
class. Then add two values to the state
object: cart
and total
. The cart
will be an array, since it may eventually hold many items. The total
will be a number. After assigning these, replace references to the values with this.state.property
:
将一个名为state
的属性添加到Product
类。 然后将两个值添加到state
对象: cart
和total
。 该cart
将是一个数组 ,因为它最终可能会容纳许多物品。 total
将是一个数字。 分配这些值后,用this.state. property
替换对值的this.state. property
this.state. property
:
import React, { Component } from 'react';
import './Product.css';
export default class Product extends Component {
state = {
cart: [],
total: 0
}
render() {
return(
<div className="wrapper">
<div>
Shopping Cart: {this.state.cart.length} total items.
</div>
<div>Total {this.state.total}</div>
<div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
<button>Add</button> <button>Remove</button>
</div>
)
}
}
Notice that in both cases, since you are referencing JavaScript inside of your JSX, you need to wrap the code in curly braces. You are also displaying the length
of the cart
array to get a count of the number of items in the array.
请注意,在这两种情况下,由于您都是在JSX中引用JavaScript,因此需要将代码括在花括号中。 您还将显示cart
数组的length
,以获取数组中项目数的计数。
Save the file. When you do, the browser will refresh and you’ll see the same page as before.
保存文件。 完成后,浏览器将刷新,您将看到与以前相同的页面。
The state
property is a standard class property, which means that it is accessible in other methods, not just the render
method.
state
属性是一个标准的类属性,这意味着可以用其他方法(不仅是render
方法)访问它。
Next, instead of displaying the price as a static value, convert it to a string using the toLocaleString
method, which will convert the number to a string that matches the way numbers are displayed in the browser’s region.
接下来,不要将价格显示为静态值,而是使用toLocaleString
方法将其转换为字符串,该方法会将数字转换为与数字在浏览器区域中的显示方式匹配的字符串。
Create a method called getTotal()
that takes the state
and converts it to a localized string using an array of currencyOptions
. Then, replace the reference to state
in the JSX with a method call:
创建一个名为getTotal()
的方法,该方法获取state
并将其使用currencyOptions
数组将其转换为本地化的字符串。 然后,用方法调用替换对JSX中state
的引用:
import React, { Component } from 'react';
import './Product.css';
export default class Product extends Component {
state = {
cart: [],
total: 0
}
currencyOptions = {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}
getTotal = () => {
return this.state.total.toLocaleString(undefined, this.currencyOptions)
}
render() {
return(
<div className="wrapper">
<div>
Shopping Cart: {this.state.cart.length} total items.
</div>
<div>Total {this.getTotal()}</div>
<div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
<button>Add</button> <button>Remove</button>
</div>
)
}
}
Since total
is a price for goods, you are passing currencyOptions
that set the maximum and minimum decimal places for your total
to two. Note that this is set as a separate property. Often, beginner React developers will put information like this in the state
object, but it is best to only add information to state
that you expect to change. This way, the information in state
will be easier to keep strack of as your application scales.
由于total
是商品价格,因此您要传递currencyOptions
,以将total
的最大和最小小数位设置为两个。 请注意,这被设置为单独的属性。 通常,初学者阵营开发商就会把信息像这样的state
对象,但最好是只添加信息state
,你期望的变化。 这样,当您的应用程序扩展时,处于state
的信息将更易于跟踪。
Another important change you made was to create the getTotal()
method by assigning an arrow function to a class property. Without using the arrow function, this method would create a new this
binding, which would interfere with the current this
binding and introduce a bug into our code. You’ll see more on this in the next step.
您进行的另一个重要更改是通过将箭头函数分配给class属性来创建getTotal()
方法。 如果不使用arrow函数,则此方法将创建一个新的this
绑定 ,这会干扰当前的this
绑定并将错误引入我们的代码中。 您将在下一步中看到更多信息。
Save the file. When you do, the page will refresh and you’ll see the value converted to a decimal.
保存文件。 完成后,页面将刷新,您会看到将值转换为小数。
You’ve now added state to a component and referenced it in your class. You also accessed values in the render
method and in other class methods. Next, you’ll create methods to update the state and show dynamic values.
现在,您已将状态添加到组件并在类中引用了它。 您还访问了render
方法和其他类方法中的值。 接下来,您将创建方法来更新状态并显示动态值。
步骤3 —从静态值设置状态 (Step 3 — Setting State from a Static Value)
So far you’ve created a base state for the component and you’ve referenced that state in your functions and your JSX code. In this step, you’ll update your product page to modify the state
on button clicks. You’ll learn how to pass a new object containing updated values to a special method called setState
, which will then set the state
with the updated data.
到目前为止,您已经为组件创建了基本状态,并且已经在函数和JSX代码中引用了该状态。 在此步骤中,您将更新产品页面以修改按钮单击时的state
。 您将学习如何将包含更新值的新对象传递给名为setState
的特殊方法,该方法随后将使用更新后的数据设置state
。
To update state
, React developers use a special method called setState
that is inherited from the base Component
class. The setState
method can take either an object or a function as the first argument. If you have a static value that doesn’t need to reference the state
, it’s best to pass an object containing the new value, since it’s easier to read. If you need to reference the current state, you pass a function to avoid any references to out-of-date state
.
为了更新state
,React开发人员使用一种称为setState
的特殊方法,该方法是从基本Component
类继承的。 setState
方法可以将对象或函数作为第一个参数。 如果您有一个不需要引用state
的静态值,则最好传递一个包含新值的对象,因为它更易于阅读。 如果您需要引用当前状态,则可以传递一个函数,以避免对过期state
任何引用。
Start by adding an event to the buttons. If your user clicks Add, then the program will add the item to the cart
and update the total
. If they click Remove, it will reset the cart to an empty array and the total
to 0
. For example purposes, the program will not allow a user to add an item more then once.
首先向按钮添加事件。 如果您的用户单击“ 添加” ,则程序会将商品添加到cart
并更新total
。 如果他们单击“ 删除” ,它将把购物车重置为一个空数组, total
为0
。 出于示例目的,该程序不允许用户再添加一个项目。
Open Product.js
:
打开Product.js
:
- nano src/components/Product/Product.js 纳米src / components / Product / Product.js
Inside the component, create a new method called add
, then pass the method to the onClick
prop for the Add button:
在组件内部,创建一个名为add
的新方法,然后将该方法传递给onClick
属性的Add按钮:
import React, { Component } from 'react';
import './Product.css';
export default class Product extends Component {
state = {
cart: [],
total: 0
}
add = () => {
this.setState({
cart: ['ice cream'],
total: 5
})
}
currencyOptions = {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}
getTotal = () => {
return this.state.total.toLocaleString(undefined, this.currencyOptions)
}
render() {
return(
<div className="wrapper">
<div>
Shopping Cart: {this.state.cart.length} total items.
</div>
<div>Total {this.getTotal()}</div>
<div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
<button onClick={this.add}>Add</button>
<button>Remove</button>
</div>
)
}
}
Inside the add
method, you call the setState
method and pass an object containing the updated cart
with a single item ice cream
and the updated price of 5
. Notice that you again used an arrow function to create the add
method. As mentioned before, this will ensure the function has the proper this
context when running the update. If you add the function as a method without using the arrow function, the setState
would not exist without binding the function to the current context.
在add
方法内部,您调用setState
方法并传递一个对象,该对象包含带有单个项目ice cream
和更新价格5
的更新cart
。 请注意,您再次使用了箭头功能来创建add
方法。 如前所述,这将确保函数在运行更新时具有正确的this
上下文。 如果在不使用箭头函数的情况下将函数添加为方法,则不将函数绑定到当前上下文就不会存在setState
。
For example, if you created the add
function this way:
例如,如果您通过以下方式创建了add
函数:
export default class Product extends Component {
...
add() {
this.setState({
cart: ['ice cream'],
total: 5
})
}
...
}
The user would get an error when they click on the Add button.
用户单击“ 添加”按钮时将收到错误消息。
Using an arrow function ensures that you’ll have the proper context to avoid this error.
使用箭头功能可确保您拥有适当的上下文,以避免出现此错误。
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.
保存文件。 完成后,浏览器将重新加载,并且当您单击“ 添加”按钮时,购物车将使用当前金额进行更新。
With the add
method, you passed both properties of the state
object: cart
and total
. However, you do not always need to pass a complete object. You only need to pass an object containing the properties that you want to update, and everything else will stay the same.
使用add
方法,您传递了state
对象的两个属性: cart
和total
。 但是,您并不总是需要传递完整的对象。 您只需要传递一个包含要更新的属性的对象,其他所有内容都将保持不变。
To see how React can handle a smaller object, create a new function called remove
. Pass a new object containing just the cart
with an empty array, then add the method to the onClick
property of the Remove button:
要查看React如何处理较小的对象,请创建一个名为remove
的新函数。 传递一个仅包含带有空数组的cart
的新对象,然后将该方法添加到“ 删除”按钮的onClick
属性中:
import React, { Component } from 'react';
import './Product.css';
export default class Product extends Component {
...
remove = () => {
this.setState({
cart: []
})
}
render() {
return(
<div className="wrapper">
<div>
Shopping Cart: {this.state.cart.length} total items.
</div>
<div>Total {this.getTotal()}</div>
<div className="product"><span role="img" aria-label="ice cream">🍦</span></div>
<button onClick={this.add}>Add</button>
<button onClick={this.remove}>Remove</button>
</div>
)
}
}
Save the file. When the browser refreshes, click on the Add and Remove buttons. You’ll see the cart update, but not the price. The total
state value is preserved during the update. This value is only preserved for example purposes; with this application, you would want to update both properties of the state
object. But you will often have components with stateful properties that have different responsibilities, and you can make them persist by leaving them out of the updated object.
保存文件。 浏览器刷新后,单击“ 添加”和“ 删除”按钮。 您会看到购物车更新,但看不到价格。 total
状态值在更新过程中保留。 保留该值仅用于示例目的; 使用此应用程序,您需要更新state
对象的两个属性。 但是,您通常会拥有带有状态属性的组件,这些组件具有不同的职责,并且可以通过将它们放在更新对象之外来使它们持久化。
The change in this step was static. You knew exactly what the values would be ahead of time, and they didn’t need to be recalculated from state
. But if the product page had many products and you wanted to be able to add them multiple times, passing a static object would provide no guarantee of referencing the most up-to-date state
, even if your object used a this.state
value. In this case, you could instead use a function.
此步骤中的更改是静态的。 您确切地知道了这些值会提前出现,并且不需要从state
重新计算它们。 但是,如果产品页面上有很多产品,并且您希望能够多次添加它们,则即使您的对象使用this.state
值,传递静态对象也不能保证引用最新state
。 在这种情况下,您可以改用一个函数。
In the next step, you’ll update state
using functions that reference the current state.
在接下来的步骤中,您将更新state
使用引用当前状态的功能。
步骤4 —使用当前状态设置状态 (Step 4 — Setting State Using Current State)
There are many times when you’ll need to reference a previous state to update a current state, such as updating an array, adding a number, or modifying an object. To be as accurate as possible, you need to reference the most up-to-date state
object. Unlike updating state
with a predefined value, in this step you’ll pass a function to the setState
method, which will take the current state as an argument. Using this method, you will update a component’s state using the current state.
很多时候,您需要引用以前的状态来更新当前状态,例如更新数组,添加数字或修改对象。 为了尽可能准确,您需要引用最新的state
对象。 与使用预定义值更新state
不同,在这一步中,您将把一个函数传递给setState
方法,该方法将当前状态作为参数。 使用此方法,您将使用当前状态更新组件的状态。
Another benefit of setting state
with a function is increased reliability. To improve performance, React may batch setState
calls, which means that this.state.value
may not be fully reliable. For example, if you update state
quickly in several places, it is possible that a value could be out of date. This can happen during data fetches, form validations, or any situation where several actions are occurring in parallel. But using a function with the most up-to-date state
as the argument ensures that this bug will not enter your code.
通过功能设置state
另一个好处是提高了可靠性。 为了提高性能,React可以批处理setState
调用,这意味着this.state. value
this.state. value
可能不完全可靠。 例如,如果您在多个位置快速更新state
,则值可能已过时。 这可能在数据获取,表单验证或并行执行多个操作的任何情况下发生。 但是使用state
为最新的函数作为参数可确保此错误不会输入您的代码。
To demonstrate this form of state management, add some more items to the product page. First, open the Product.js
file:
为了演示这种形式的状态管理,请在产品页面中添加更多项目。 首先,打开Product.js
文件:
- nano src/components/Product/Product.js 纳米src / components / Product / Product.js
Next, create an array of objects for different products. The array will contain the product emoji, name, and price. Then loop over the array to display each product with an Add and Remove button:
接下来,为不同的产品创建对象数组。 该数组将包含产品表情符号,名称和价格。 然后在数组上循环以显示带有“ 添加和删除”按钮的每个产品:
import React, { Component } 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 class Product extends Component {
...
render() {
return(
<div className="wrapper">
<div>
Shopping Cart: {this.state.cart.length} total items.
</div>
<div>Total {this.getTotal()}</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={this.add}>Add</button>
<button onClick={this.remove}>Remove</button>
</div>
))}
</div>
</div>
)
}
}
In this code, you are using the map()
array method to loop over the products
array and return the JSX that will display each element in your browser.
在这段代码中,您使用map()
数组方法遍历products
数组并返回将在浏览器中显示每个元素的JSX。
Save the file. When the browser reloads, you’ll see an updated product list:
保存文件。 重新加载浏览器后,您会看到更新的产品列表:
Now you need to update your methods. First, change the add()
method to take the product
as an argument. Then instead of passing an object to setState()
, pass a function that takes the state
as an argument and returns an object that has the cart
updated with the new product and the total
updated with the new price:
现在,您需要更新您的方法。 首先,更改add()
方法以将product
作为参数。 然后,而不是将对象传递给setState()
,而传递一个函数,该函数将state
作为参数并返回一个对象,该对象的cart
已使用新产品更新,而total
金额已使用新价格更新:
import React, { Component } from 'react';
import './Product.css';
...
export default class Product extends Component {
state = {
cart: [],
total: 0
}
add = (product) => {
this.setState(state => ({
cart: [...state.cart, product.name],
total: state.total + product.price
}))
}
currencyOptions = {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}
getTotal = () => {
return this.state.total.toLocaleString(undefined, this.currencyOptions)
}
remove = () => {
this.setState({
cart: []
})
}
render() {
return(
<div className="wrapper">
<div>
Shopping Cart: {this.state.cart.length} total items.
</div>
<div>Total {this.getTotal()}</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={() => this.add(product)}>Add</button>
<button onClick={this.remove}>Remove</button>
</div>
))}
</div>
</div>
)
}
}
Inside the anonymous function that you pass to setState()
, make sure you reference the argument—state
—and not the component’s state—this.state
. Otherwise, you still run a risk of getting an out-of-date state
object. The state
in your function will be otherwise identical.
在传递给setState()
的匿名函数内部,请确保引用了参数this.state
而不是组件的state
this.state
。 否则,您仍然存在获取过期state
对象的风险。 否则,功能中的state
将相同。
Take care not to directly mutate state. Instead, when adding a new value to the cart
, you can add the new product
to the state
by using the spread syntax on the current value and adding the new value onto the end.
注意不要直接改变状态。 而是在向cart
添加新值时,可以通过对当前值使用传播语法并将新值添加到末尾来将新product
添加到state
。
Finally, update the call to this.add
by changing the onClick()
prop to take an anonymous function that calls this.add()
with the relevant product.
最后,通过更改onClick()
属性以使用对相关产品调用this.add()
的匿名函数来更新对this.add
的调用。
Save the file. When you do, the browser will reload and you’ll be able to add multiple products.
保存文件。 完成后,浏览器将重新加载,您将可以添加多个产品。
Next, update the remove()
method. Follow the same steps: convert setState
to take a function, update the values without mutating, and update the onChange()
prop:
接下来,更新remove()
方法。 遵循相同的步骤:将setState
转换为一个函数,更新值而不进行更改,并更新onChange()
属性:
import React, { Component } from 'react';
import './Product.css';
...
export default class Product extends Component {
...
remove = (product) => {
this.setState(state => {
const cart = [...state.cart];
cart.splice(cart.indexOf(product.name))
return ({
cart,
total: state.total - product.price
})
})
}
render() {
return(
<div className="wrapper">
<div>
Shopping Cart: {this.state.cart.length} total items.
</div>
<div>Total {this.getTotal()}</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={() => this.add(product)}>Add</button>
<button onClick={() => this.remove(product)}>Remove</button>
</div>
))}
</div>
</div>
)
}
}
To avoid mutating the state object, you must first make a copy of it using the spread
operator. Then you can splice out the item you want from the copy and return the copy in the new object. By copying state
as the first step, you can be sure that you will not mutate the state
object.
为避免更改状态对象,您必须首先使用spread
运算符对其进行复制。 然后,您可以从副本中拼接出所需的项目,然后将副本返回到新对象中。 通过复制state
作为第一步,可以确保您不会突变state
对象。
Save the file. When you do, the browser will refresh and you’ll be able to add and remove items:
保存文件。 完成后,浏览器将刷新,您将能够添加和删除项目:
There is still a bug in this application: In the remove
method, a user can subtract from the total
even if the item is not in the cart
. If you click Remove on the ice cream without adding it to your cart, your total will be -5.00.
此应用程序中仍然存在一个错误:在remove
方法中,即使项目不在cart
,用户也可以从total
减去。 如果您在冰淇淋上单击删除而不将其添加到购物车,则总计为-5.00 。
You can fix the bug by checking for an item’s existence before subtracting, but an easier way is to keep your state object small by only keeping references to the products and not separating references to products and total cost. Try to avoid double references to the same data. Instead, store the raw data in state
— in this case the whole product
object—then perform the calculations outside of the state
.
您可以通过在减去之前检查项目的存在来修复该错误,但是更简单的方法是通过仅保留对产品的引用而不是对产品和总成本的引用来使状态对象较小。 尝试避免重复引用相同的数据。 取而代之的是,将原始数据存储在state
(在这种情况下为整个product
对象),然后在state
之外执行计算。
Refactor the component so that the add()
method adds the whole object, the remove()
method removes the whole object, and the getTotal
method uses the cart
:
重构组件,以便add()
方法添加整个对象, remove()
方法删除整个对象,而getTotal
方法使用cart
:
import React, { Component } from 'react';
import './Product.css';
...
export default class Product extends Component {
state = {
cart: [],
}
add = (product) => {
this.setState(state => ({
cart: [...state.cart, product],
}))
}
currencyOptions = {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}
getTotal = () => {
const total = this.state.cart.reduce((totalCost, item) => totalCost + item.price, 0);
return total.toLocaleString(undefined, this.currencyOptions)
}
remove = (product) => {
this.setState(state => {
const cart = [...state.cart];
const productIndex = cart.findIndex(p => p.name === product.name);
if(productIndex < 0) {
return;
}
cart.splice(productIndex, 1)
return ({
cart
})
})
}
render() {
...
}
}
The add()
method is similar to what it was before, except that reference to the total
property has been removed. In the remove()
method, you find the index of the product
with findByIndex
. If the index doesn’t exist, you’ll get a -1
. In that case, you use a conditional statement toreturn nothing. By returning nothing, React will know the state
didn’t change and won’t trigger a re-render. If you return state
or an empty object, it will still trigger a re-render.
add()
方法与之前的方法类似,不同之处在于已删除了对total
属性的引用。 在remove()
方法,你找到的指数product
与findByIndex
。 如果索引不存在,您将得到-1
。 在这种情况下,您可以使用条件语句不返回任何内容。 通过不返回任何内容,React将知道state
没有改变并且不会触发重新渲染。 如果返回state
或空对象,它仍会触发重新渲染。
When using the splice()
method, you are now passing 1
as the second argument, which will remove one value and keep the rest.
使用splice()
方法时,现在将1
作为第二个参数传递,它将删除一个值并保留其余值。
Finally, you calculate the total
using the reduce()
array method.
最后,您可以使用reduce()
数组方法计算total
。
Save the file. When you do, the browser will refresh and you’ll have your final cart
:
保存文件。 完成后,浏览器将刷新,您将获得最终的cart
:
The setState
function you pass can have an additional argument of the current props, which can be helpful if you have state that needs to reference the current props. You can also pass a callback function to setState
as the second argument, regardless of if you pass an object or function for the first argument. This is particularly useful when you are setting state
after fetching data from an API and you need to perform a new action after the state
update is complete.
您传递的setState
函数可以具有当前道具的附加参数,如果您有需要引用当前道具的状态,则该函数很有帮助。 您也可以将回调函数作为第二个参数传递给setState
,而不管是否为第一个参数传递对象或函数。 当您从API提取数据后设置state
,并且在state
更新完成后需要执行新操作时,此功能特别有用。
In this step, you learned how to update a new state based on the current state. You passed a function to the setState
function and calculated new values without mutating the current state. You also learned how to exit a setState
function if there is no update in a manner that will prevent a re-render, adding a slight performance enhancement.
在此步骤中,您学习了如何根据当前状态更新新状态。 您将一个函数传递给setState
函数,并在不改变当前状态的情况下计算了新值。 您还学习了如何在没有更新的情况下退出setState
函数,该更新将阻止重新渲染,从而略微提高了性能。
结论 (Conclusion)
In this tutorial, you have developed a class-based component with a dynamic state that you’ve updated statically and using the current state. You now have the tools to make complex projects that respond to users and dynamic information.
在本教程中,您开发了具有动态状态的基于类的组件,该组件已静态更新并使用当前状态。 现在,您有了工具来制作响应用户和动态信息的复杂项目。
React does have a way to manage state with Hooks, but it is helpful to understand how to use state on components if you need to work with components that must be class-based, such as those that use the componentDidCatch
method.
React确实有一种使用Hooks管理状态的方法,但是,如果需要使用必须基于类的组件(例如使用componentDidCatch
方法的componentDidCatch
,了解如何在组件上使用状态很有帮助。
Managing state is key to nearly all components and is necessary for creating interactive applications. With this knowledge you can recreate many common web components, such as sliders, accordions, forms, and more. You will then use the same concepts as you build applications using hooks or develop components that pull data dynamically from APIs.
管理状态是几乎所有组件的关键,对于创建交互式应用程序是必需的。 有了这些知识,您就可以重新创建许多常见的Web组件,例如滑块,手风琴,表格等等。 然后,您将使用与使用钩子构建应用程序或开发从API动态提取数据的组件相同的概念。
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-on-react-class-components
react无状态组件