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
廣告