[ 筆記 ] React 02 - 狀態 state、setState、props


Posted by krebikshaw on 2020-10-20

State

在 React 裡面最重要的觀念就是它的 state 會對應到一個 UI,用 7-11 比喻,就好像我們要檢查某品牌飲料的數量時,State 就會是後台的紀錄,而這個 State 會對應到架上的數量。只是在 React 裡面,一但 state 發生變動、就會自動 call render()。

現在要開始學習怎麼幫 Component 加上 state,有兩種方法可以新增 state:

☞ 第一種:直接寫

直接寫在 Component 裡面,缺點是因為這是比較新的語法,所以需要安裝額外的 plugin @babel/plugin-proposal-class-properties 才支援,嫌麻煩的話可以用另一種寫法。

class App extends Component {
  state = {
    counter : 1
  }
}

☞ 第二種:寫在 constructor 裡面

還記得物件導向的寫法吧,把 state 加在 constructor 裡面,且因為 App 是繼承自 Component,所以要記得加上 super() 去呼叫 Component 的 constructor,不然會報錯。

class App extends Component {
  constructor() {
    super(); // => 記得呼叫 parent 的 constructor,很重要
    this.state = { // => 幫 App 加上 state
      counter : 1
    }
  }
  render() {
    return (
      <div>
        <p>{this.state.counter}</p>
      </div>
    )
  }
}

改變 State

要改變 Component 的狀態 state 必須要 call this.setState(),裡面傳一個 object,因為我們無法直接對 this.state.counter 做改變:

class App extends Component {
  constructor() {
    super();
    this.state = {
      title: '點我改變 state',
      counter : 1
    }
  }
  render() {
    return (
      <div>
        <h1 onClick={() => {
          // this.state.counter++; => 無法這麼做
          this.setState({
            counter: this.state.counter + 1 // => 更改 state 必須 call this.setState()
          })
        }}>{this.state.title}</h1>

        // => 顯示目前 state
        <p>{this.state.counter}</p> 
      </div>
    )
  }
}

而因為這樣的寫法會看起來有點混亂,所以通常會把改變 state 的程式抽出一個 function,在這邊寫成一個 handleClick:

class App extends Component {
  constructor() {
    super();
    this.state = {
      title: '點我改變 state',
      counter : 1
    }
  }

  // => 控制 App 的 state
  handleClick() {
    this.setState({
      counter: this.state.counter + 1
    })
  }

  render() {
    return (
      <div>
        // => 當 click 再呼叫 this.handleClick
        <h1 onClick={this.handleClick.bind(this)}>{this.state.title}</h1>
        <p>{this.state.counter}</p>
      </div>
    )
  }
}
  • 這邊在呼叫 handleClick() 的時候,要加上一個 .bind(this)

因為 this 的值是在呼叫 function 的當下決定的,在宣告的時候我們不能確定 this 的值是什麼,所以當我們執行按下 Title 執行 this.handleClick 的 function 時,就只是單純執行一個 function,而不是以 App 去呼叫 this.handleClick。而因為打包出來的 bundle.js 是在嚴格模式下 "use strict";,所以 this 當然就是 undefined。
所以才要利用 bind() 來把 this 指定回 App 這個 Component。

先使用 bind() 把 this 值決定好,無論怎麼呼叫 this.handleClick 這個 function,this 的值都會是 App 這個 Component 本身。

  • 但如果使用的是 arrow function 情況會不同,因為 arrow function 中的 this 是在定義 function 的時候就決定好的,所以外層的 this 是什麼、arrow function 的 this 就是什麼

Props

相較於 state 比較像自己內部的狀態,還有一個屬性叫做 props,可以當作是外部傳進來的狀態。用 7-11 比喻,就好像我們要讓某牌飲料上架之前,要先以超商原本的配置來將飲料做歸類,假設原本超商有三台冰箱,放飲料的是 A、B 兩台,我想要把這款飲料放在 A 冰箱的第 3 層,我可能就在這牌飲料的「後台紀錄」先設定它之後要上架的位置是 A-3,這就是我在外部給它傳入的狀態。

回到 React 來舉例:假設我們有一個用來製作按鈕的 Component,我們希望在表單下方放上兩個按鈕,我們要用相同的 Component,但是一個按鈕上面寫的是「確定」一個按鈕上面寫的是「返回」。如此一來我們就要在放上這個 Component 的時候,從外部設定說我希望它顯示的文字為何。這個從外部設定狀態的動作,就是用 props

上面程式碼寫的 Counter,我們把它抽出來獨立成一個 Component,那這樣要怎麼傳入改變後的狀態給 Counter 這個 Component 呢?

class Counter extends Component {
  render() {
    // => 拿到剛剛傳進來的 number 屬性值
    return <div>{this.props.number}</div>; 
  }
}

class App extends Component {
  constructor() {
    super();
    this.state = {
      title: '點我改變 state',
      counter : 1
    }

    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
  render() {
    return (
      <div>
        <h1 onClick={this.handleClick}>{this.state.title}</h1>
        // => 用 number 當成一個屬性傳進去 Counter 這個 Component
        <Counter number={this.state.counter}/> 
      </div>
    )
  }
}

運作流程就是:

  1. 原來的 App 的 state.counter => 1
    • 畫面輸出 1
  2. 點了一下 Title 觸發 this.handleClick,改變了 App 的 state
    • state.counter => 2
  3. 然而 App 的 state 只要一改變,就會重新觸發 App 的 render()
    • 畫面輸出 2

記得 React 的好處:資料改變了,畫面會自動改變

props.children

相較於剛剛指定一個屬性名稱,其實還有另一種方式傳遞 props.children,好處是可以傳任意元素進去( 字串、HTML 元素、Component… )。

class Title extends Component {
  render() {
    console.log(this.props.children); // => 輸出會看到兩個 props 屬性:(2) [{…}, "I'm Title"]
    return (
      this.props.children
    )
  }  
}
class App extends Component {
  render() {
    return (
      <div>
        <Title> 
          <h1>I'm h1 title</h1>  // => 夾在標籤底下的都是 props.children 的內容
          {`I'm Title`}
        </Title>
      </div>
    )
  }
}

小記

這篇文章講了兩個重點 State & Props,一個是在「內部」設定狀態,一個由「外部」決定狀態,兩個狀態之間是不會相互擾的。

就像飲料要上架之前,先從外部設定狀態為(它上架的位置在 A-3,價格設定為 30 元/瓶),上架之後可以改變內部狀態(架子上面有 10 瓶,庫存有 300 瓶)

在 React 裡面,只要 state 一改變,就會更新 UI ,也就是觸發 render()


#State #Props







Related Posts

[ Day 07 ] 來用 Docker 運行 InstaPy 吧!

[ Day 07 ] 來用 Docker 運行 InstaPy 吧!

MTR04_0803

MTR04_0803

[4] 測試是一門學問,關於執行測試

[4] 測試是一門學問,關於執行測試


Comments