ReactJS - 无状态组件



具有内部状态的 React 组件称为 Stateful 组件,没有任何内部状态管理的 React 组件称为 Stateless 组件。React 建议创建和使用尽可能多的无状态组件,并且仅在绝对必要时创建有状态组件。此外,React 不与子组件共享状态。数据需要通过子组件的属性传递给子组件。

将日期传递给 FormattedDate 组件的示例如下 -


 <FormattedDate value={this.state.item.spend_date} />

总体思路是不要使应用程序逻辑过于复杂,只在必要时才使用高级功能。

创建有状态组件

让我们创建一个 React 应用程序来显示当前日期和时间。

第 1 步 - 首先,按照创建React应用程序一章中的说明,使用Create React App或Rollup打包器创建一个新的react应用程序react-clock-app。

在您最喜欢的编辑器中打开应用程序。

第 2 步 - 在应用程序的根目录下创建src文件夹。

在 src 文件夹下创建 components 文件夹。

创建一个文件,Clock.js src/components 文件夹下并开始编辑。

导入 React 库。


 import React from 'react';

接下来,创建 Clock 组件。


class Clock extends React.Component {	
	 	constructor(props) {	
	 	 	 super(props);	
	 	}	
}

第 3 步 - 使用当前日期和时间初始化状态。


constructor(props) {	
	 	super(props);	
	 	this.state = {	
	 	 	 date: new Date()	
	 	}	
}

第 4 步 - 添加方法setTime()以更新当前时间 -


setTime() {	
	 	console.log(this.state.date);	
	 	this.setState((state, props) => (
	 	 	 {
	 	 	 	 	date: new Date()	
	 	 	 }	
	 	))	
}

第 5 步 - 使用 JavaScript 方法,setInterval 并每秒调用 setTime() 方法,以确保组件的状态每秒更新一次。


constructor(props) {	
	 	super(props);	
	 	this.state = {	
	 	 	 date: new Date()	
	 	}	
	 	setInterval( () => this.setTime(), 1000);	
}

第 6 步 - 创建渲染函数。


render() {
}
Next, update the render() method to show the current time.
render() {
	 	return (
	 	 	 <div><p>The current time is {this.state.date.toString()}</p></div>
	 	);
}

最后,导出组件。

export default Clock;

Clock组件的完整源代码如下:


import React from 'react';

class Clock extends React.Component {
	 	constructor(props) {
	 	 	 super(props);
	 	 	 this.state = {
	 	 	 	 	date: new Date()
	 	 	 } 	 	 	
	 	 	 setInterval( () => this.setTime(), 1000);
	 	}
	 	setTime() {
	 	 	 console.log(this.state.date);
	 	 	 this.setState((state, props) => (
	 	 	 	 	{
	 	 	 	 	 	 date: new Date()
	 	 	 	 	}
	 	 	 ))
	 	}
	 	render() {
	 	 	 return (
	 	 	 	 	<div>
	 	 	 	 	 	 <p>The current time is {this.state.date.toString()}</p>
	 	 	 	 	</div>
	 	 	 );
	 	}
}
export default Clock;

index.js

接下来,创建一个文件,index.js src 文件夹下并使用 Clock 组件。


import React from 'react';
import ReactDOM from 'react-dom';
import Clock from './components/Clock';

ReactDOM.render(
	 	<React.StrictMode>
	 	 	 <Clock />
	 	</React.StrictMode>,
	 	document.getElementById('root')
);

index.html

最后,在根文件夹下创建一个公共文件夹,并创建index.html文件。


<!DOCTYPE html>
<html lang="en">
	 	<head>
	 	 	 <meta charset="utf-8">
	 	 	 <title>Clock</title>
	 	</head>
	 	<body>
	 	 	 <div id="root"></div>
	 	 	 <script type="text/JavaScript" src="./index.js"></script>
	 	</body>
</html>

使用 npm 命令为应用程序提供服务。

npm start

打开浏览器,在地址栏中输入 http://localhost:3000,然后按回车键。该应用程序将显示时间并每秒更新一次。

The current time is Wed Nov 11 2020 10:10:18 GMT+0530(Indian Standard Time)

上述应用程序运行良好,但在控制台中抛出错误。

Can't call setState on a component that is not yet mounted.

错误消息指示只有在挂载组件后才必须调用 setState。

什么是安装?

React 组件有一个生命周期,挂载是生命周期中的一个阶段。让我们在接下来的章节中更多地了解生命周期。

在费用管理器应用程序中引入状态

让我们通过添加一个简单的功能来删除费用项目,从而在费用管理器应用程序中引入状态管理。

第 1 步 - 在您最喜欢的编辑器中打开费用管理器应用程序。

打开ExpenseEntryItemList.js文件。

使用通过属性传递到组件中的费用项目初始化组件的状态。


 this.state = { items: this.props.items }

第 2 步 - 在 render() 方法中添加 Remove 标签。


<thead>
	 	<tr>
	 	 	 <th>Item</th>
	 	 	 <th>Amount</th>
	 	 	 <th>Date</th>
	 	 	 <th>Category</th>
	 	 	 <th>Remove</th>
	 	</tr>
</thead>

第 3 步 - 更新 render() 方法中的列表以包含删除链接。此外,使用状态中的项 (this.state.items) 而不是属性 (this.props.items) 中的项。


const lists = this.state.items.map((item) =>
	 	<tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
	 	 	 <td>{item.name}</td>
	 	 	 <td>{item.amount}</td>
	 	 	 <td>{new Date(item.spendDate).toDateString()}</td>
	 	 	 <td>{item.category}</td>
	 	 	 <td><a href="#" onClick={(e) => 	this.handleDelete(item.id, e)}>Remove</a></td>
	 	</tr>
);

第 4 步 - 实施 handleDelete 方法,该方法将从状态中删除相关的费用项目。


handleDelete = (id, e) => {
	 	e.preventDefault();
	 	console.log(id);

	 	this.setState((state, props) => {
	 	 	 let items = [];

	 	 	 state.items.forEach((item, idx) => {
	 	 	 	 	if(item.id != id)
	 	 	 	 	 	 items.push(item)
	 	 	 })
	 	 	 let newState = {
	 	 	 	 	items: items
	 	 	 }
	 	 	 return newState;
	 	})
}

这里

  • 支出项目是从组件的当前状态中获取的。
  • 将循环访问当前支出项目,以查找用户使用项目 ID 引用的项目。
  • 创建一个新项目列表,其中包含除用户推荐的项目以外的所有支出项目

第 5 步 - 添加新行以显示总费用金额。


<tr>
	 	<td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
	 	<td colSpan="4" style={{ textAlign: "left" }}>
	 	 	 {this.getTotal()}
	 	</td>	
</tr>

第 6 步 - 实施 getTotal() 方法以计算总费用金额。


getTotal() {
	 	let total = 0;
	 	for(var i = 0; i < this.state.items.length; i++) {
	 	 	 total += this.state.items[i].amount
	 	}
	 	return total;
}

render()方法的完整代码如下:


render() {
	 	const lists = this.state.items.map((item) =>
	 	 	 <tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
	 	 	 	 	<td>{item.name}</td>
	 	 	 	 	<td>{item.amount}</td>
	 	 	 	 	<td>{new Date(item.spendDate).toDateString()}</td>
	 	 	 	 	<td>{item.category}</td>
	 	 	 	 	<td><a href="#"	
	 	 	 	 	 	 onClick={(e) => 	this.handleDelete(item.id, e)}>Remove</a></td>
	 	 	 </tr>
	 	);
	 	return (
	 	 	 <table onMouseOver={this.handleMouseOver}>
	 	 	 	 	<thead>
	 	 	 	 	 	 <tr>
	 	 	 	 	 	 	 	<th>Item</th>
	 	 	 	 	 	 	 	<th>Amount</th>
	 	 	 	 	 	 	 	<th>Date</th>
	 	 	 	 	 	 	 	<th>Category</th>
	 	 	 	 	 	 	 	<th>Remove</th>
	 	 	 	 	 	 </tr>
	 	 	 	 	</thead>
	 	 	 	 	<tbody>
	 	 	 	 	 	 {lists}
	 	 	 	 	 	 <tr>
	 	 	 	 	 	 	 	<td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
	 	 	 	 	 	 	 	<td colSpan="4" style={{ textAlign: "left" }}>
	 	 	 	 	 	 	 	 	 {this.getTotal()}
	 	 	 	 	 	 	 	</td>	
	 	 	 	 	 	 </tr>
	 	 	 	 	</tbody>
	 	 	 </table>
	 	);
}

最后,ExpenseEntryItemList的更新代码如下:


import React from 'react';
import './ExpenseEntryItemList.css';

class ExpenseEntryItemList extends React.Component {
	 	constructor(props) {
	 	 	 super(props);
	 	 	 this.state = {
	 	 	 	 	items: this.props.items
	 	 	 }
	 	 	 this.handleMouseEnter = this.handleMouseEnter.bind();
	 	 	 this.handleMouseLeave = this.handleMouseLeave.bind();
	 	 	 this.handleMouseOver = this.handleMouseOver.bind();
	 	}
	 	handleMouseEnter(e) {
	 	 	 e.target.parentNode.classList.add("highlight");
	 	}
	 	handleMouseLeave(e) {
	 	 	 e.target.parentNode.classList.remove("highlight");
	 	}
	 	handleMouseOver(e) {
	 	 	 console.log("The mouse is at (" + e.clientX + ", " + e.clientY + ")");
	 	}
	 	handleDelete = (id, e) => {
	 	 	 e.preventDefault();
	 	 	 console.log(id);
	 	 	 this.setState((state, props) => {
	 	 	 	 	let items = [];
	 	 	 	 	state.items.forEach((item, idx) => {
	 	 	 	 	 	 if(item.id != id)
	 	 	 	 	 	 	 	items.push(item)
	 	 	 	 	})
	 	 	 	 	let newState = {
	 	 	 	 	 	 items: items
	 	 	 	 	}
	 	 	 	 	return newState;
	 	 	 })
	 	}
	 	getTotal() {
	 	 	 let total = 0;
	 	 	 for(var i = 0; i < this.state.items.length; i++) {
	 	 	 	 	total += this.state.items[i].amount
	 	 	 }
	 	 	 return total;
	 	}
	 	render() {
	 	 	 const lists = this.state.items.map((item) =>
	 	 	 	 	<tr key={item.id} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
	 	 	 	 	 	 <td>{item.name}</td>
	 	 	 	 	 	 <td>{item.amount}</td>
	 	 	 	 	 	 <td>{new Date(item.spendDate).toDateString()}</td>
	 	 	 	 	 	 <td>{item.category}</td>
	 	 	 	 	 	 <td><a href="#"	
	 	 	 	 	 	 	 	onClick={(e) => 	this.handleDelete(item.id, e)}>Remove</a></td>
	 	 	 	 	</tr>
	 	 	 );
	 	 	 return (
	 	 	 	 	<table onMouseOver={this.handleMouseOver}>
	 	 	 	 	 	 <thead>
	 	 	 	 	 	 	 	<tr>
	 	 	 	 	 	 	 	 	 <th>Item</th>
	 	 	 	 	 	 	 	 	 <th>Amount</th>
	 	 	 	 	 	 	 	 	 <th>Date</th>
	 	 	 	 	 	 	 	 	 <th>Category</th>
	 	 	 	 	 	 	 	 	 <th>Remove</th>
	 	 	 	 	 	 	 	</tr>
	 	 	 	 	 	 </thead>
	 	 	 	 	 	 <tbody>
	 	 	 	 	 	 	 	{lists}
	 	 	 	 	 	 	 	<tr>
	 	 	 	 	 	 	 	 	 <td colSpan="1" style={{ textAlign: "right" }}>Total Amount</td>
	 	 	 	 	 	 	 	 	 <td colSpan="4" style={{ textAlign: "left" }}>
	 	 	 	 	 	 	 	 	 	 	{this.getTotal()}
	 	 	 	 	 	 	 	 	 </td>	
	 	 	 	 	 	 	 	</tr>
	 	 	 	 	 	 </tbody>
	 	 	 	 	</table>
	 	 	 );
	 	}
}
export default ExpenseEntryItemList;

index.js

更新index.js并包含 ExpenseEntyItemList 组件。


import React from 'react';
import ReactDOM from 'react-dom';
import ExpenseEntryItemList from './components/ExpenseEntryItemList'

const items = [
	 	{ id: 1, name: "Pizza", amount: 80, spendDate: "2020-10-10", category: "Food" },
	 	{ id: 2, name: "Grape Juice", amount: 30, spendDate: "2020-10-12", category: "Food" },
	 	{ id: 3, name: "Cinema", amount: 210, spendDate: "2020-10-16", category: "Entertainment" },
	 	{ id: 4, name: "Java Programming book", amount: 242, spendDate: "2020-10-15", category: "Academic" },
	 	{ id: 5, name: "Mango Juice", amount: 35, spendDate: "2020-10-16", category: "Food" },
	 	{ id: 6, name: "Dress", amount: 2000, spendDate: "2020-10-25", category: "Cloth" },
	 	{ id: 7, name: "Tour", amount: 2555, spendDate: "2020-10-29", category: "Entertainment" },
	 	{ id: 8, name: "Meals", amount: 300, spendDate: "2020-10-30", category: "Food" },
	 	{ id: 9, name: "Mobile", amount: 3500, spendDate: "2020-11-02", category: "Gadgets" },
	 	{ id: 10, name: "Exam Fees", amount: 1245, spendDate: "2020-11-04", category: "Academic" }
]
ReactDOM.render(
	 	<React.StrictMode>
	 	 	 <ExpenseEntryItemList items={items} />
	 	</React.StrictMode>,
	 	document.getElementById('root')
);

使用 npm 命令为应用程序提供服务。

npm start

接下来,打开浏览器并在地址栏中输入 http://localhost:3000,然后按回车键。

最后,要删除支出项目,请单击相应的删除链接。它将删除相应的项目并刷新用户界面,如动画 gif 中所示。

Interface