iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 5
3
Modern Web

React 30天系列 第 5

Day 05-靈魂角色的家庭成員(Props & State)

前情提要:
昨天我們建立了一個Button component,也在裡面偷渡了props的用法(昨日傳送門)。了解了component組合之一的Function Component之後,今天就來介紹Class Component吧,也可以一起把state跟大家介紹。

Function Components轉成Class Component是容易做到的,但要由Class Components轉成Function Components就有一點難度了,原因有二:

  1. Class Components允許擁有state,但Function Component不行
  2. Class Components可以綁定React的生命週期(lifecycle),可以在Component掛載、更新或卸載等不同階段阻擋做一些事...對...一些你要托付它的事。

我們今天先來講class和state,明天再來好好認識生命週期(lifecycpe)。
這人每天都在埋伏筆好煩啊!
https://media.giphy.com/media/RBeddeaQ5Xo0E/giphy.gif

官方教學無痛轉換五步驟,教你如何從function轉成class:

  1. 建立一個ES6的class(類別),這個class是React.Component的擴展子類別
  2. 增加一個名為render的empty method
  3. 把function的內容移到render裡
  4. 在render method裡把props改成this.props
  5. 把function砍掉說永別
// class component
class Button extends React.Component { // 1. class Xxx extends React.Component {}
  render() { // 2. render() {...}
    return ( // 3. return {...}
      (<div>{this.props.text}</div>) // 4. this.props ...
    );
  }
}
// function component 5. 移除以下程式碼
cnost Button = (props) => {
  return (
    <div>{props.text}</div>
  );
}

還記得我們在開始的時候有提到兩個class component轉function component困難的原因之一嗎?
Class Components允許擁有state,但Function Component不行
所以state在component內該怎麼使用呢?

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {  // 初始化 state
      text: 'hahaha'
    }
  }
  render() {
    return (<div>{this.state.text}</div>);  // 使用 state
  }
}

在上面的程式碼中,我們透過在constructor(建構子))裡建立的this.state初始化我們的state資料,然後再透過this.state.text取得text的值。

等等!那個constructor(建構子)是什麼?如果不認得的話大概就是Javascript沒學好,畢竟連React官網都直接放MDN Constructor的link了QAQ,以下簡單整理:

  1. constructor是一個特別的方法(method)來建立和初始化一個類別(class)的物件(object)
  2. 一個類別(class)只會有一個constructor,一個以上它會報SyntaxError
  3. 建構子(constructor)可以透過super來呼class的父類別(super class)

另外,關於constructor(),React是這麼說的,如果沒有用到state也不用bind methods的話,我們也不需要使用constructor了,下面附上原文出處讓大家參考:

If you don’t initialize state and you don’t bind methods, you don’t need to implement a constructor for your React component.
出處:React.Component-constructor

那constrouctor和super內的props又是怎麼一回事?事情是這樣的,constructor是在react component被掛載(mounted)前就被呼叫的,所以不使用super(props)的話,props在constructor的範疇內會是undefined呦。
undefined情況
https://ithelp.ithome.com.tw/upload/images/20181012/20111595moWc9F3lX9.png
正常運作情況
https://ithelp.ithome.com.tw/upload/images/20181012/20111595vieoS2z84c.png

補充

Class components should always call the base constructor with props.
出處:State and Lifecycle

結論:要嘛就不要用constructor,要用constructor,props就是標配。

上面講了一大堆javascript範疇的東西,果然是基礎沒打好煩惱一大票...(自我感嘆)
接著我們來看看state在component內可以做些什麼吧!
以我們Button的例子來看,如果今天我...想做一個計數器(弱爆ˊ_>ˋ),外部不需要傳資料進來,我只需要更新內部數量就好,那要怎麼實作呢?

試做一個計數器

需求

  • 按鈕每點擊一次數量加一
  • 計數器本身為一個獨立的component

首先,在src/components下建立一個新的counter.js,我們把component獨立出來

  1. 建立一個Class Component
  2. 建立一個constructor,並初始化state並指定count初始值為0(設定count來記錄我們的點擊次數)
  3. 建立render method,我們需要可以顯示數字的h1 element和可供點擊的button element
  4. 設定點擊事件(click event)更新count
  5. 增加一個叫做addCount的function處理click event
  6. 把addCount這個function丟到constructor綁定component instance
import React, { Component } from "react";

class Counter extends Component { // 1. 建立Class Component
  constructor(props) {  // 2. 初始化state並指定count初始值為0
    super(props);
    this.state = {  
      count: 0
    };
    this.addCount = this.addCount.bind(this);  // 6. 綁定component instance
  }

  addCount() {  // 5. 處理click event
    this.setState({ count: this.state.count + 1 });
  }

  render() {  // 3.
    return (
      <div>
        <h1>{this.state.count}</h1>
        <button onClick={this.addCount}>點擊+1</button> {/* 4.設定點擊事件 */}
      </div>
    );
  }
}

export default Counter;

然後我們回到index.js匯入我們的Counter component

import React from 'react';
import ReactDOM from 'react-dom';

import Counter from './src/components/counter';  // 匯入Counter

const App = () => {
  return (
    <div>
      <Counter/> { /* 使用Counter component */}
    </div>
  );
};

ReactDOM.render(<App/>, document.getElementById('app'));

在terminal輸入yarn start,開啟localhost:8080,操作畫面如下:
https://i.imgur.com/kdXUzdf.gif

以上就是state的用法了!完整程式碼請參照github傳送門

補充之一
剛剛bind(綁定)function到instance的做法是Bind in Constructor (ES2015)
另外React還介紹了其他方法>How do I bind a function to a component instance?
以下列出記錄一下
Bind in Constructor (ES2015)

class Foo extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}

Class Properties (Stage 3 Proposal)

class Foo extends Component {
  // Note: 此語法還是實驗性,尚未標準化
  handleClick = () => {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick}>Click Me</button>;
  }
}

以下兩種bind方法會在每次render的時候就建立一個新的function,有效能上的影響
Bind in Render

class Foo extends Component {
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <button onClick={this.handleClick.bind(this)}>Click Me</button>;
  }
}

Arrow Function in Render

class Foo extends Component {
  handleClick() {
    console.log('Click happened');
  }
  render() {
    return <button onClick={() => this.handleClick()}>Click Me</button>;
  }
}

補充之二 2018/10/14更新
你該知道的三件關於setState()的大小事

  • 使用setState來修改state
    • 錯誤用法:this.state.comment = 'Hello';
    • 正確用法:this.setState({comment: 'Hello'});
  • 更新state可能是非同步的 (setState()可接受objectfunction)
    React可以批次處理多個setState()然後一次更新以提高效能。
    因為this.props和this.state可能是非同步更新,我們不該倚賴他們的value來計算新的state,所以解法為使用function接收state和props。
    錯誤用法
    // Wrong
    this.setState({
      counter: this.state.counter + this.props.increment,
    });
    
    正確用法 第一個參數為上次收到的state;第二個參數為當時更新的props
    // Correct
    this.setState((state, props) => ({
      counter: state.counter + props.increment
    }));
    
  • state的更新是獨立的。如果我的state有list和name兩項資料,我在setState更新name時,不會影響list的資料,反之亦同。

總結今日:

  • Class Components和Function Components有兩項差異,Class Components
    • 允許擁有state
    • 可以綁定React的生命週期(lifecycle)
  • state用法
    • 初始化 : this.state = { xxx: 'xxx', ... }
    • 使用state: this.state.xxx
    • 更新state: this.setState({ xxx: 'ccc' })
  • 關於constructor
    • 初始化的state要放在constructor裡面
    • 如果沒有用到state也不用bind methods的話,不需要使用constructor
    • 有constructor就需要設定super(props),官網建議標配
    • constructor透過super來呼class的父類別(super class)
    • super(props)的使用目的為在constructor內可使用this.props

本日完結,有任何錯誤再請大家指正。


上一篇
Day 04-React的靈魂角色(Components)
下一篇
Day 06-生命有限好好把握(Lifecycle)
系列文
React 30天30

尚未有邦友留言

立即登入留言