React - 三大屬性 State

React - 三大屬性 State

從 0 開始學 React

前言

介紹組件 三大屬性中的 State 屬性,這裡還不會討論 Hook 運用。

React 起手式

  1. 老規矩,先建立一個 HTML,並搭建好環境

    還不知道 HTML 起手式請點我

創建組件,完成基礎頁面

  1. 在 script 中,要引用 React 的三大屬性,若不考慮使用 Hook ,則需使用類式組件。

    1
    2
    3
    4
    5
    6
    class Vaccine extends React.Component {
    render(){
    return <h2>你打疫苗了嗎? 還沒QQ</h2>
    }
    }
    ReactDOM.render( <Vaccine/>, document.getElementById( 'container' ) )
  2. 畫面呈現

    你打疫苗了嗎? 還沒QQ

創建建構子,初始化狀態

  1. 在類式組件內添加建構子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Vaccine extends React.Component {

    // 建構子,寫 props 傳遞參數
    constructor(props){
    // 建構子內要寫 super 接收 props,不然會報錯
    super(props)
    // 初始化狀態,官方規定要寫物件型式
    this.state = { vaccinated: true }
    }


    render(){
    // 這裡把回答變成 JS 表達式 {},並使用用條件運算子
    return (
    <h2>你打疫苗了嗎?
    { this.state.vaccinated ? ' 已經打了(.▽.) b' : ' 還沒(Q A Q)' }
    </h2>)
    }
    }
    ReactDOM.render( <Vaccine/>, document.getElementById( 'container' ) )
  2. 因為我們把vaccinated:設為true,所以條件運算子就會選:前面的字串。

    true 畫面

  3. 如果我們把vaccinated:改成false,那麼條件運算子就會選擇:後面的字串。

    false 畫面

  4. 這時打開開發者工具的 Component,可以看到state

    State 狀態

  5. 最後再更進階一點,把代碼更加精簡,將render內提取狀態進行解構賦值,讓程式更容易閱讀。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    render(){
    // 在這裡先進行解構賦值,讀取狀態
    const { vaccinated } = this.state
    // 這樣後面就可以直接利用變數,不用再加前墜
    return (
    <h2>你打疫苗了嗎?
    { vaccinated ? ' 已經打了(.▽.) b' : ' 還沒(Q A Q)' }
    </h2>)
    }

嘗試為頁面新增互動

類式組件中方法的 this 指向?

  1. 幫頁面中的<h2>新增onClick事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class Vaccine extends React.Component {

    constructor(props){
    super(props)
    this.state = { vaccinated: false }
    }


    render(){
    const { vaccinated } = this.state
    // 用來測試作為實例調用是否正常
    this.changeVaccinated()

    // 幫 <h2> 新增 onClick 事件,
    // 需注意 React 的事件綁定改成用駝峰命名法,
    // 並且調用方法要用 {} 包起來,
    // 且要用 this 來調用函數,結尾不能加 ()
    // 有加 () 會變成一開頁面就立即執行
    return (
    <h2 onClick={ this.changeVaccinated }>你打疫苗了嗎?
    { vaccinated ? ' 已經打了(.▽.) b' : ' 還沒(Q A Q)' }
    </h2>)
    }
    /* 接續下面 */
  2. 並在類式組件內增加onClick所需調用的函數

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
        /* 承接上面 */

    // 將 changeVaccinated 放進 Vaccine 的原型變數裡,供實例使用
    // 通過 Vaccine 實例調用 changeVaccinated 時

    // 由於 changeVaccinated 是作為 onClick 的'回調函數'
    // 所以不是通過'實例'調用,而是'直接'調用。

    // 類中的方法默認開啟了局部的嚴格模式,
    // 所以 changeVaccinated 中的 this 為 undefined
    changeVaccinated(){
    console.log(this)
    }
    }
    ReactDOM.render( <Vaccine/>, document.getElementById( 'container' ) )
  3. 點擊你打疫苗了嗎?後,打開開發者工具 Console ,觀察 this 回傳值

    組件內 this 的指向

  4. 總結 this 指向結果,作為onClick直接調用函數會無法指向實例。

    this 調用

解決類式組件中方法 this 指向的問題

  1. 只要在constructor中,多加一行程式碼就能解決 this 指向問題

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Vaccine extends React.Component {

    constructor(props){
    super(props)
    this.state = { vaccinated: false }

    // 增加這一行,解決 changeVaccinated 中的 this 指向
    // 右邊的 this.changeVaccinated 會先找到自身原型上的 changeVaccinated
    // 調用了 bind(this) 方法,並把 this 綁在實例身上
    // 然後將這個方法放到自身新增的 changeVaccinated (左邊)
    this.changeVaccinated = this.changeVaccinated.bind(this)
    }
  2. 點擊你打疫苗了嗎?後,打開開發者頁面的 Console,就會看到 this 指向實例,並且添加了changeVaccinated方法

    ![bind(this) 解決指向問題]](https://i.imgur.com/jJl2WYw.png)

這段程式碼整個流程如下圖

這段程式碼整個流程如下圖

bind() 方法做了什麼?

bind() 方法做了兩件事
1. 會建立一個新的函數。
2. 會幫你改函數裡面的 this

小測驗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Youtube extends React.Component {

constructor(props){
super(props)
this.state = {
subscribed: false,
youtube: 'https://www.youtube.com/channel/UC-b2nGm0xLzic38Byti0VjA',
}
this.____A____ = this.____B____.bind(this)
}

render(){
const { subscribed, youtube } = this.state

return (
<h2 onClick={ this.subscribe }>你訂閱六角學院的 Youtube 了嗎?
{ subscribed ? ' σ`∀´)σ 訂閱了,哪次不訂閱' :
' 我就幫到這了(ㆆᴗㆆ) ' + youtube }
</h2>)
}

gogogo(){
console('趕快去唄 =͟͟͞͞( •̀д•́)')
}
}
ReactDOM.render( <Youtube/>, document.getElementById( 'container' ) )

A 和 B 答案分別是甚麼 不知道的話請點我

1
右邊有答案-----------------------------------------------------------------------------A: subscribe / B: gogogo!

嘗試新增互動,剛學 React 必踏入過一次的坑

  1. 首先取得 state 裡面的vaccinated

    1
    2
    3
    changeVaccinated(){
    const vaccinated = this.state.vaccinated
    }
  2. 嘗試修改 state

    1
    2
    3
    4
    5
    6
    changeVaccinated(){
    const vaccinated = this.state.vaccinated
    this.state.vaccinated = !vaccinated
    // 用來測試是否有更改值
    console.log(this.state.vaccinated)
    }
  3. 你會發現值確實有修改,但畫面不會變

    你會發現值確實有修改,但畫面不會變

  4. 注意狀態( state )不可以直接修改
    this.state.vaccinated = !vaccinated 這行就是直接修改

使用 setState 來修改狀態

  1. 一樣先取得 state 裡面的vaccinated

    1
    2
    3
    changeVaccinated(){
    const vaccinated = this.state.vaccinated
    }
  2. 使用 setState() 方法,
    注意狀態( state )必須透過 setState 進行修改

    1
    2
    3
    4
    changeVaccinated(){
    const vaccinated = this.state.vaccinated
    this.setState({ vaccinated: !vaccinated })
    }
  3. 這時畫面就能點擊切換

    成功觸發切換

state 的簡寫方式

使用類的基礎知識簡化 state

  1. 這是原來的程式碼

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Vaccine extends React.Component {

    constructor(props){
    super(props)
    this.state = { vaccinated: false }
    this.changeVaccinated = this.changeVaccinated.bind(this)
    }


    render(){
    const { vaccinated } = this.state
    return (
    <h2 onClick={ this.changeVaccinated }>你打疫苗了嗎?
    { vaccinated ? ' 已經打了(.▽.) b' : ' 還沒(Q A Q)' }
    </h2>)
    }

    changeVaccinated(){
    const vaccinated = this.state.vaccinated
    this.setState({ vaccinated: !vaccinated })
    }
    }
    ReactDOM.render( <Vaccine/>, document.getElementById( 'container' ) )
  2. 將 constructor 中的 state 拉出來

    • 類中可以直接寫賦值語句 = 往實例裡面追加屬性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Vaccine extends React.Component {

    constructor(props){
    super(props)
    // this.state = { vaccinated: false }
    this.changeVaccinated = this.changeVaccinated.bind(this)
    }

    // 使用賦值語句,拉出 constructor
    state = { vaccinated: false }

    /* 以下省略 */
  3. 將函數改成賦值語句 + ES6 箭頭函數的寫法,並省略 constructor 裡面的 bind()

    利用箭頭函數的特點

    • 沒有自己的 this
    • 使用 this 時,會找其外側函數的 this,作為箭頭函數 this 去使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
        /* 以上省略 */

    // changeVaccinated(){
    // const vaccinated = this.state.vaccinated
    // this.setState({ vaccinated: !vaccinated })
    // }

    // 改成 ES6 箭頭函數
    changeVaccinated = () => {
    const vaccinated = this.state.vaccinated
    this.setState({ vaccinated: !vaccinated })
    }
    }
    ReactDOM.render( <Vaccine/>, document.getElementById( 'container' ) )
  4. 省略整個 constructor,最後簡化結果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Vaccine extends React.Component {
    // 初始化狀態
    state = { vaccinated: false }

    render(){
    const { vaccinated } = this.state
    return (
    <h2 onClick={ this.changeVaccinated }>你打疫苗了嗎?
    { vaccinated ? ' 已經打了(.▽.) b' : ' 還沒(Q A Q)' }
    </h2>)
    }
    // 自定義方法 - 藥用賦值語句的型式 + 箭頭函數
    changeVaccinated = () => {
    const vaccinated = this.state.vaccinated
    this.setState({ vaccinated: !vaccinated })
    }
    }
    ReactDOM.render( <Vaccine/>, document.getElementById( 'container' ) )

總結 State

什麼是 State ?

  1. State 是組件中最重要的屬性,值是物件(可以包含多個 key-value)。
  2. 通過更新 State 重新渲染組件。

State 必須注意的要點

  1. 組件中 render 方法中的 this 為組件的實例物件。

  2. 組件自訂義的函數中,this 為undefined如何解決?

    • 強制綁定 this:通過函數bind()方法
    • 賦值語句 + 箭頭函數
  3. 狀態資料,不能直接修改或更新,必須借助setState()來更新

參考資料