ReactJS - 无状态组件



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

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

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

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

创建一个有状态组件

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

步骤 1 - 首先,使用Create React AppRollup捆绑器创建一个新的 React 应用程序react-clock-app,方法是按照创建 React 应用程序章节中的说明进行操作。

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

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

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

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

导入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函数。

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

接下来,在 src 文件夹下创建一个文件index.js并使用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

最后,在根文件夹下创建一个public文件夹并创建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

打开浏览器并在地址栏中输入https://:3000并按 Enter 键。应用程序将显示时间并每秒更新一次。

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 - 在您喜欢的编辑器中打开expense-manager应用程序。

打开ExpenseEntryItemList.js文件。

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

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

步骤 2 - 在render()方法中添加删除标签。

<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

接下来,打开浏览器并在地址栏中输入https://:3000并按 Enter 键。

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

Interface
广告

© . All rights reserved.