Class Component

Class Component

2020/09/26 (重新整理)

Class Component

React的元件有兩種形式,前面我們介紹了Functional Component,另一種是以Class Component的方式開發。

我們來了解一下class component的基本運作原理,首先,我們先來改一下cick.js,現在是functional component的形式:

import React, {useState, useEffect} from 'react';

import {Alert, Button} from 'react-native';


export default function Click(props) {

const [count, setCount] = useState(props.count);


let countString = "count:"+count;


function showCount(){

Alert.alert("count:"+count);

props.update(count);

}

useEffect(showCount);

return (

<Button title={countString} onPress={()=>setCount(count+1)}/>

);

}

先從一個空的類別開始,首先,會用到react的Component類別,所有的class component都是Component類別的子類別。對於類別的說明,請詳參: Javascript

./src/Click.js

import React, {Component} from 'react';


export default class Click extends Component {


}

Render

Component裡有很多個內建的方法,最基本的方法是render (詳參: Rendering an Element into the DOM)。在class component裡,原本functional component在return裡的內容就要放在render的return裡,也就是override Component的render,意思是,react其實會呼叫render方法。

./src/Click.js

import React, {Component} from 'react';

import {Button} from 'react-native';


export default class Click extends Component {


render() {

return (

<Button title='Hello'/>

);

}


}

State

在class component裡,跟java一樣有建構式。

export default class Click extends Component {

constructor(){

super();

}

跟functional component一樣有state變數,差別是state變數一定要命名為state。另外,使用類別變數就要加上this,就要寫成this.state.count。(詳參:State 和生命週期)

不同的是functional component利用useState來管理state變數,但是,在class component裡是直接操作state變數。

import React, {Component} from 'react';

import {Button} from 'react-native';


export default class Click extends Component {

constructor(){

super();

this.state={count:0};

}


render() {

return (

<Button title= {"count:"+this.state.count;}/>

);

}


}

更動state變數,就必須使用setState()。因為,state是個物件,所以,必須是:

this.setState({count:this.state.count+1});

./src/Click.js

import React, {Component} from 'react';

import {Button} from 'react-native';


export default class Click extends Component {

constructor(){

super();

this.state={count:0};

}


handleClick(){

this.setState({count:this.state.count+1});

}


render() {

return (

<Button title= {"count:"+this.state.count} onPress={this.handleClick}/>

);

}


}

可是,卻會得到:

this.setState is 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';

import {Button} from 'react-native';


export default class Click extends Component {

constructor(){

super();

this.state={count:0};

}


handleClick(){

this.setState({count:this.state.count+1});

}


render() {

return (

<Button title= {"count:"+this.state.count} onPress={this.handleClick.bind(this)}/>

);

}

}


第二種解決方法是利用arrow function (現在的範例都這麼寫),呼叫handleClick的時候就不需要bind了:

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

import React, {Component} from 'react';

import {Button} from 'react-native';


export default class Click extends Component {

constructor(){

super();

this.state={count:0};

}


handleClick = ()=>{

this.setState({count:this.state.count+1});

}


render() {

return (

<Button title= {"count:"+this.state.count} onPress={this.handleClick}/>

);

}

}

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

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>


);

}

}

到這裡,就會發現使用functional component的確是比class component簡單。

Props

class component接受props的方式也不太一樣,跟functional component不同的是,必須使用this.props來接收props:

import React, {Component} from 'react';

import {Button} from 'react-native';


export default class Click extends Component {

constructor(){

super();

console.log(this);

this.state={count:0};

}


handleClick = ()=>{

this.setState({count:this.props.count+1});

}


render() {

return (

<Button title= {"count:"+this.state.count} onPress={this.handleClick}/>

);

}

}

如果在constructor裡使用this.props,會有問題,因為這時候還沒有收到props:

import React, {Component} from 'react';

import {Button} from 'react-native';


export default class Click extends Component {

constructor(){

super();

console.log(this);

this.state={count:this.props.count};//ERROR!! this.props.count undefined

}


handleClick = ()=>{

this.setState({count:this.props.count+1});

}


render() {

return (

<Button title= {"count:"+this.state.count} onPress={this.handleClick}/>

);

}

}

這時候,就必須介紹class component的生命週期了。

生命週期

在class components還有一些內建的method,其中一個常用的method就是componentDidMount,componentDidMount會在UI render之後被呼叫。

import React, {Component} from 'react';

import {Button} from 'react-native';


export default class Click extends Component {

constructor(){

super();

this.state={count:0};

console.log("in constructor");

}


componentDidMount() {

console.log("Mounted");

//this.setState({count:this.props.count});

}

handleClick = ()=>{

this.setState({count:this.props.count+1});

}


render() {

console.log("rendering");

return (

<Button title= {"count:"+this.state.count} onPress={this.handleClick}/>

);

}

}

console.log的輸出是:

in constructor

rendering

Mounted

如果在componentDidMount裡執行setState,因為state變數被異動,render會被再呼叫一次。

import React, {Component} from 'react';

import {Button} from 'react-native';


export default class Click extends Component {

constructor(){

super();

this.state={count:0};

console.log("in constructor");

}


componentDidMount() {

console.log("Mounted");

this.setState({count:this.props.count});

}

handleClick = ()=>{

this.setState({count:this.props.count+1});

}


render() {

console.log("rendering");

return (

<Button title= {"count:"+this.state.count} onPress={this.handleClick}/>

);

}

console.log的輸出是:

in constructor

rendering

Mounted

rendering

簡單的說,useEffect就是functional component裡對應componentDidMount的function。細節請詳參: State 和生命週期React.Component

./src/Click.js

import React, {Component} from 'react';

import {Button} from 'react-native';


export default class Click extends Component {

constructor(){

super();

this.state={count:0};

}


handleClick = () => {

this.setState(prevState => {

return ({

count: prevState.count + 1

});

});

}

componentDidMount() {

this.setState({count:this.props.count});

}


render() {

return (

<Button title= {"count:"+this.state.count} onPress={this.handleClick}/>

);

}

}

參考資料

Class 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