Class Component

Class Component

2019/09/30 (新增作業)

2019/10/19 (新增連結)

Class Component

React的元件有兩種形式,第一種是以Class Component的方式開發,最近,由於React Hooks,也讓Functional Component的開發方式成為另一種選項。建議初學者先了解Class Component的開發方式,等了解Class Component了,再進一步了解Functional Component的開發方式。

我們來了解一下react class component的基本運作原理,首先,我們先來改一下index.js,把App改為ProductList

import React from 'react';
import ReactDOM from 'react-dom';
import ProductList from './components/product-list';

ReactDOM.render(<ProductList />,document.getElementById('root'));

再新增一個./components/product-list.js,這是一個react component,所以,要繼承react提供的Component類別。

import React, {Component} from 'react';

export default class ProductList extends Component {

}

Render

component裡有很多個內建的方法,先使用前面用過的render (詳參: Rendering an Element into the DOM)。

./components/product-list.js

import React, {Component} from 'react';

export default class ProductList extends Component {

 render() {
  return (
   <ul>
    <li>0 / iPad / 20000</li> 
    <li>1 / iPhone X / 30000</li>
   </ul>
  );
 }

}

如果我們要把內容存在一個物件裡,我們會寫:

import React, {Component} from 'react';

export default class ProductList extends Component {
  product = {id:"0", desc:"iPad", price:20000};


 render() {
  return (
   <ul>
    <li>{product.id} / {product.desc} / {product.price}</li> 
    <li>1 / iPhone X / 30000</li>
   </ul>
  );
 }

}

結果,居然有錯誤訊息說找不到這個變數,是因為在javascript類別的函數裡,存取類別裡的變數要加this:

import React, {Component} from 'react';

export default class ProductList extends Component {
  product = {id:"0", desc:"iPad", price:20000};

 render() {
  return (
   <ul>
    <li>{this.product.id} / {this.product.desc} / {this.product.price}</li> 
    <li>1 / iPhone X / 30000</li>
   </ul>
  );
 }

}

改成陣列,為了讓程式能比較精簡(不寫迴圈),採用javascript陣列裡的map方法,並利用javascript的箭頭函式 / arrow function :

import React, {Component} from 'react';

export default class ProductList extends Component {

 products= [
  {id:"0", desc:"iPad", price:20000},
  {id:"1", desc:"iPhone X", price:30000}
 ];


 render() {
  return (
   <ul>
    {this.products.map(product => <li>{product.id} / {product.desc} / {product.price}</li>)}
   </ul>
  );
 }

}

State

當資料內容是靜態的不會被改變的話,這樣的寫法是可以的,但是,如果資料內容會被改變的話,就必須將資料放在state裡,在後面(Event)會解釋。(詳參:State 和生命週期)

./components/product-list.js

import React, {Component} from 'react';

export default class ProductList extends Component {
  state = {
   products: [
    {id:"0", desc:"iPad", price:20000},
    {id:"1", desc:"iPhone X", price:30000}
   ]
 }

 render() {
  return (
   <ul>
    { this.state.products.map(product => <li>{product.id} / {product.desc} / {product.price}</li>)} 
   </ul>
  )
 }
}

Props

當我們的介面越來越複雜的時候,或者有些內容想要共用的時候,就可以再產生一個Product元件,並且把資料(product)利用Props傳進那個元件的product (詳參:Components 與 Props),其中,大括號中的變數名稱要和arrow function的名稱一樣。例如:

this.state.products.map(p => <Product product={p}/>)

另外,前面的product就是傳進Product元件後的變數名稱。

<Product product={product}/>

./components/product-list.js

import React, {Component} from 'react';
import Product from './product';

export default class ProductList extends Component {


 state = {
  products: [
   {id:"0", desc:"iPad", price:20000},
   {id:"1", desc:"iPhone X", price:30000}
  ]
 }

 render() {
  return (
   <ul>
    { this.state.products.map(product => <Product product={product}/>)} 
   </ul>
  )
 }

}

在Product裡,就可以利用this.props.product取得傳進來的product。現在看起來這個元件似乎很沒有必要,但是,當內容較複雜時(如:加了連結 或style),這樣就能把內容作一些切割,另外,Product元件也可以被其他元件重複使用。

product.js:

import React, {Component} from 'react';

 
export default class Product extends Component{

  render(){
    return (      
      <li>{this.props.product.id} / {this.props.product.desc} / {this.props.product.price}</li>
    )
    
  }
    
}

Event & State

index.js

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

ReactDOM.render(<Click />,document.getElementById('root'));

在react(JSX)是利用onClick來定義事件跟HTML裡呼叫javascript的函數是不一樣的。

components/click.js


import React, {Component} from 'react';

export default class Click extends Component {
 
 constructor(){
  super();
  this.count = 0; 
 }
    
 handleClick(){
  alert(this.count);
  this.count++;
 }

 render() {
  return (
   <button onClick={this.handleClick}>{this.count}</button>

  );
 }

}

結果,會看到錯誤:

TypeError: Cannot read property 'count' of undefined

原來,是在react裡,自訂的函數裡是所取得的this變數不是Click類別裡的變數 (React 與 bind this 的一些心得 / React Binding Patterns: 5 Approaches for Handling `this`),所以,this是存在的,但是並不是Click類別所產生的物件,解決的方法是加上bind。(詳參:This is why we need to bind event handlers in Class Components in React (ES6))

import React, {Component} from 'react';

export default class Click extends Component {
 
 constructor(){
  super();
  this.count = 0; 
 }
    
 handleClick(){
  alert(this.count);
  this.count++;
 }

 render() {
  return (
   <button onClick={this.handleClick.bind(this)}>{this.count}</button>

  );
 }

}

結果,每次點選時,都可以看到數字增加了,可是,按鈕上的數字還是停留在0。這是因為,我們只是更改了變數內容,但這樣的更動並不會重新呼叫render(),改變按鈕上的內容。所以,就必須使用state。

import React, {Component} from 'react';

export default class Click extends Component {

  
 constructor(){
  super();
  this.state={counter:0};
 }
    
 handleClick(){
  alert(this.state.count);
  this.state.count++;
  }
 render() {
  return (
   <button onClick={this.handleClick.bind(this)}>{this.state.count}</button>

  );
 }

}

馬上就有錯誤了(如果沒有看到錯誤的警告,也會發現按鈕的數字並沒有改變),因為在react裡,要透過setState來更動State裡的內容,才會重新呼叫render(),改變按鈕上的內容。

import React, {Component} from 'react';

export default class Click extends Component {

  
 constructor(){
  super();
  this.state={count:0};
 }
    
 handleClick(){
  alert(this.state.count);
  this.setState({count:this.state.count+1}); 
 }

 render() {
  return (
   <button onClick={this.handleClick.bind(this)}>{this.state.count}</button>

  );
 }

}

稍微再優化一下,利用arrow function (現在的範例都這麼寫),呼叫handleClick的時候就不需要bind了:

(詳參:This is why we need to bind event handlers in Class Components in React (ES6))

import React, {Component} from 'react';

export default class Click extends Component {
  
 constructor(){
  super();
  this.state={count:0};
 }
    
 handleClick = () => {
  this.setState({count:this.state.count+1}); 
 }

 render() {
  return (
   <button onClick={this.handleClick}>{this.state.count}</button>

  );
 }

}

根據官方文件(State 和生命週期)及一些文章 (如: React Components 該注意的6個地方與技巧 / React Counter Button),如果是要利用前一次的內容來計算下一次的內容,可能會有問題,最好是使用第二種形式的 setState(),它接受一個 function 而不是一個 object。

import React, {Component} from 'react';

export default class Click extends Component {

  
 constructor(){
  super();
  this.state={count:0};
 }
    
  handleClick = () => {
  this.setState(prevState => {
   return ({
    count: prevState.count + 1
   });
  });
 }

 render() {
  return (
   <button onClick={this.handleClick}>{this.state.count}</button>

  );
 }
 
}

作業

請改寫範例,新增產品類型、庫存量、安全存量等欄位:

  • 編號 / id (int)
  • 產品描述 / desc (String)
  • 價格 / price (int)
  • 產品類型 / category (String) (個人電腦、筆記型電腦、平板電腦、智慧型手機)
  • 庫存量 / inventory (int)
  • 安全存量 / safetyStock (int)

參考資料

Class Component

 class NameOfComponent extends Component {

to

function NameOfComponent(props){