[React筆記]組件三大核心屬性之 State

終於學到了組件實例的三大核心屬性之一 State

本篇內容為看影片學習做的筆記,影片為 尚硅谷2021版React技術全家桶

既然提到的是組件”實例“的三大核心屬性,那就代表是類式組件了,畢竟函數式組件連類都沒有,談何實例呢。(其實 hooks 也可以讓函數式組件用 state、props 和 refs,這邊暫且不提)

這邊整理幾個關於state的小重點,建議可以學完後面再來回顧:

  • state 是組件物件最重要的屬性,值是物件(可以包含多個 key-value 的組合)
  • 組件被稱為”狀態機”,通過更新組件的 state 來更新對應的頁面提示(重新渲染組件)。
  • 組件中 render 方法中的 this 為組件實例物件。
  • 組件自定義的方法中 this 為undefined,如何解決?
    • 強制綁定 this:通過函數物件的 bind()
    • 箭頭函數
  • State 的值,不能直接修改或更新,必須使用 setState({})

改變組件 state 值

現在我們要來完成一個簡單的小題目,從中學習 State :

要讓畫面上呈現 今天天氣很炎熱,並且單擊鼠標左鍵時,炎熱兩個字會替換成涼爽

zfCEAT3 [React筆記]組件三大核心屬性之 State

不是 炎熱 就是 涼爽,這種非黑即白的概念可以透過 boolean 去控制:

isHot:true => 炎熱
isHot:false => 涼爽

我們得想辦法把isHot放進 state裡面,然後將它讀出來進行判斷,因此在最開始,我們要先思考如何將寫到 state 去。

想對類的實例化進行操作,比如增加一個屬性或修改一個屬性的值,代表我們需要去寫建構子。

一樣是通過官方的範例來寫:

constructor(props){
    super(props)
    this.state = {isHot:true}
}
state-1

將 state 設置為 isHot 值後,再去寫回傳的內容

render() {
    return (
        <h1>今天天氣很{this.state.isHot ? '炎熱' : '涼爽'}</h1>
    );
}

這時候我們可以看到 state 已經有 isHot: true

state-2

現在我們只差點擊一下就切換狀態,而在說之前,我們先複習一下原生事件綁定方法。

原生事件綁定

假設我們今天有三個按鈕,當我點擊這三個按鈕都能跳出訊息視窗:

<button id="btn1">按鈕1</button>
<button id="btn2">按鈕2</button>
<button>按鈕3</button>

我可以用 addEventListener 去監聽事件

const btn1 = document.getElementById('btn1')
btn1.addEventListener('click',()=>{
    alert('按鈕1被點擊')
})

const btn2 = document.getElementById('btn2')
btn2.addEventListener('click',()=>{
    alert('按鈕2被點擊')
})

或者我也可以直接在 button 中加上 onclick屬性,去調用我自己寫的一個方法

<button onclick="demo()">按鈕3</button>
function demo() {
    alert('按鈕3被點擊')
}
eZQZCnw [React筆記]組件三大核心屬性之 State

事實上,在React中這三種方式也都能用,只是React更推薦直接在標籤中寫上onclick,也就是按鈕 3 的寫法。

那麼我們就把原生的方法丟進來試試:

<script type="text/babel">
    class Weather extends React.Component {
        constructor(props){
            super(props)
            // 初始化狀態
            this.state = {isHot:true,wind:'微風'}
        }
        render() {
            // 讀取狀態
            const {isHot} = this.state
            return (
                <h1 id="title">今天天氣很{isHot ? '炎熱' : '涼爽'}</h1>
            );
        }
    }
    // 渲染組件到頁面
    ReactDOM.render(
    <Weather/>,
    document.getElementById('test')
    )
    const title = document.getElementById('title')
    title.addEventListener('click',()=>{
        console.log('標題被點擊')
    })
</script>

實際上,是可以實現的

[React筆記]組件三大核心屬性之 State

又或者直接寫onclick也是可以的:

title.onclick = () =>{
    console.log('標題被點擊')
}

但是,都用上React了卻還是一直去使用 document ,這不合適,所以我們應該使用下面這種方法。

加上一個方法

function demo() {
    alert('標題被點擊')
}

將 h1 標籤裡面加上 onClick

render() {
    // 讀取狀態
    const {isHot} = this.state
    return (
        <h1 onClick="demo()">今天天氣很{isHot ? '炎熱' : '涼爽'}</h1>
    );
}

然而,卻報錯了:

CoY61fp [React筆記]組件三大核心屬性之 State

因為 onClick 要接收的並不是 string 類型,我們需要改成:

<h1 onClick={demo()}>今天天氣很{isHot ? '炎熱' : '涼爽'}</h1>

這下可以了,但我們又會發現,我們都還沒去點標題就已經出現了訊息視窗

mmCLf6Y [React筆記]組件三大核心屬性之 State

因為寫成 {demo()} 代表要把 demo() 的返回值給onClick作為回調,然而我們的demo方法並沒有返回值(其實有,是undefined),因此點的時候回調的是 undefined,而底層發現是undefined就沒有別的動作。

所以我們必須改成 {demo},代表我把右邊的函數交給onClick作為回調,等我點擊的時候幫我調用 demo 方法。

class Weather extends React.Component {
        constructor(props){
            super(props)
            // 初始化狀態
            this.state = {isHot:true}
        }
        render() {
            // 讀取狀態
            const {isHot} = this.state
            return (
                <h1 onClick={demo}>今天天氣很{isHot ? '炎熱' : '涼爽'}</h1>
            );
        }
    }
    // 渲染組件到頁面
    ReactDOM.render(
    <Weather/>,
    document.getElementById('test')
    )

    function demo() {
        console.log('標題被點擊')
    }
mdrWJJ1 [React筆記]組件三大核心屬性之 State

但是把按鍵 function 寫在外面很雜很難看,所以我們把它丟進類中,並且記得把 function 拔掉:

class Weather extends React.Component {
    constructor(props){
        super(props)
        this.state = {isHot:true}
    }
    render() {
        // 讀取狀態
        const {isHot} = this.state
        return (
            <h1 onClick={this.changeWeather}>今天天氣很{isHot ? '炎熱' : '涼爽'}</h1>
        );
    }

    changeWeather() {
        // changeWeather放在哪裡?  Weather的原型物件上 供實例使用
        // 由於 changeWeather 是作為onClick的回調 , 所以不是通過實例調用的, 是直接調用
        // 類中的方法默認開啟了局部的嚴格模式, 所以 changeWeather 中的 this 為 undefined
        console.log(this)
    }
}
// 渲染組件到頁面
ReactDOM.render(
    <Weather/>,
    document.getElementById('test')
)

但是現在這樣還是錯誤的。

因為 changeWeather 放在 Weather 的原型物件上,供實例使用,而目前 this.changeWeather 自身還沒有實例,但它會順著原型找到原型身上的。

這時候我們需要用到 bind,bind的用處為:

  1. 生成新的函數
  2. 修改函數裡的this

因此需要將建構子改為:

constructor(props){
    super(props)
    this.state = {isHot:true}
    this.changeWeather = this.changeWeather.bind(this)
}

bind(this) :因為是放在建構子中,所以裡面的 this 指的是 Weather 的實例物件。如此一來,你的實例物件自身就多了一個 changeWeather 方法

又或者,將名字改一改會比較好去理解:

constructor(props){
    super(props)
    this.state = {isHot:true}
    this.nice = this.changeWeather.bind(this)
}
render() {
    // 讀取狀態
    const {isHot} = this.state
    return (
        <h1 onClick={this.nice}>今天天氣很{isHot ? '炎熱' : '涼爽'}</h1>
    );
}

changeWeather() {
    console.log(this)
}

接著就是要來改 state 的值了。

如果直接這樣去寫:

const isHot = this.state.isHot
this.state.isHot = !isHot

理論上沒問題,實際也並沒有問題,但是React不認可這種直接更改。

因為這屬於直接更改,React並不認可,雖然它是可行的不會報錯,但是不會真正去修改state的值。

在官方文檔中的”正確的使用State”有提到:

setstate

因此我們需要透過 setState({}) 去更新 state的值:

const isHot = this.state.isHot
// state 不可直接更改 , state 必須透過 setState進行更新
this.setState({
    isHot:!isHot
})

最後,完整的程式碼為:

class Weather extends React.Component {
        constructor(props){
            super(props)
            this.state = {isHot:true}
            this.changeWeather = this.changeWeather.bind(this)
        }

        render() {
            const {isHot} = this.state
            return (
                <h1 onClick={this.changeWeather}>今天天氣很{isHot ? '炎熱' : '涼爽'}</h1>
            );
        }

        changeWeather() {
            const isHot = this.state.isHot
            // state 不可直接更改 , state 必須透過 setState進行更新
            this.setState({
                isHot:!isHot
            })

        }
    }
    // 渲染組件到頁面
    ReactDOM.render(
    <Weather/>,
    document.getElementById('test')
    )
zfCEAT3 [React筆記]組件三大核心屬性之 State

學得頭疼…哈…

簡化程式碼

接著讓我們來簡化一下建構子

constructor(props){
    super(props)
    this.state = {isHot:true,wind:'微風'}
    this.changeWeather = this.changeWeather.bind(this)
}

我們可以把固定的屬性直接寫在類中,像是這樣

constructor(props){
    super(props)
}
state = {isHot:true,wind:'微風'}
changeWeather = this.changeWeather.bind(this)

確實可以這麼去寫,並且建構子中沒東西了還可以直接刪掉。

但是有個更精簡的寫法,甚至不需要這行 changeWeather = this.changeWeather.bind(this)

那就是將 changeWeather 方法改成箭頭函數:

changeWeather = () => {
    const isHot = this.state.isHot
    this.setState({
        isHot:!isHot
    })
}

為什麼?

箭頭函數的特性是它沒有自己的 this,但如果在箭頭函數中使用 this 並不會報錯,反而它會找其外頭函數的 this 作為箭頭函數的 this 去使用 (這邊的外側 -> 類中)。

因此可以完美的代替原本的 changeWeather = this.changeWeather.bind(this) 這句。

所以最後我們可以精簡成:

class Weather extends React.Component {
    state = {isHot:true,wind:'微風'}
    render() {
        const {isHot,wind} = this.state
        return (
            <h1 onClick={this.changeWeather}>今天天氣很{isHot ? '炎熱' : '涼爽'} , {wind}</h1>
        );
    }
    changeWeather = () => {
        const isHot = this.state.isHot
        this.setState({
            isHot:!isHot
        })
    }
}

ReactDOM.render(
    <Weather/>,
    document.getElementById('test')
)

所以以後,當我們要在類中寫自定義方法,要用賦值語句的形式然後加上箭頭函數。

完事。

參考資料

4e52d54f6bc42abb41d26eb5b0df6517?s=250&d=wavatar&r=g [React筆記]組件三大核心屬性之 State
0 0 評分數
Article Rating
訂閱
通知
guest
0 Comments
在線反饋
查看所有評論