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}/>
);
}
}
參考資料
Components in 專題知識分享
箭頭函式 / arrow function :
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)