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)
參考資料
- Components in 專題知識分享
- Rendering an Element into the DOM
- map
- 箭頭函式 / arrow function :
- State 和生命週期
- Components 與 Props
- 事件處理
- Bind
Class Component
- 激戰 ReactJS 30天 (2018)
- 30天React從入門到入坑 (2018)
- Convert Class Component to Functional Component
class NameOfComponent extends Component {
to
function NameOfComponent(props){
- remove the constructor
- remove the render() method, keep the return
- add const before all methods
- remove this.state throughout the component
- remove all references to ‘this’ throughout the component
- Set initial state with useState()
- change this.setState() … instead, call the function that you named in the previous step to update the state…
- replace compentDidMount with useEffect
- replace componentDidUpdate with useEffect
- React Hooks: Migration from Class to Function Components
- Component State with React's useState Hook
- Component Side-Effects with React's useEffect Hook
- Abstraction with Custom React Hooks
- 5 Ways to Convert React Class Components to Functional Components w/ React Hooks
- Class without state or lifecycle methods
- Class with state
- Class with multiple state properties
- Class with state and componentDidMount
- Class with state, componentDidMount and componentDidUpdate
- Convert PureComponent to React memo
- Converting React Class Components to Function Components using Hooks (ToDo app)