iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 10
0
自我挑戰組

React 30 天學習歷程系列 第 10

【Day 10】元件狀態管理 (一) state & setState

state 是 React 中元件的狀態,在元件內 state 是一個 object。state 又能分為內部狀態和外部狀態,內部狀態即前面所述的元件內的 object,外部狀態是指從元件外傳進來的資料狀態,外部狀態一般是透過第三方套件做資料管理。另外要注意的是,上一篇有提到過的 function component 本身是沒有生命週期的,也不能使用 state,但是 function component 可以透過 react hook 來使用 state 及生命週期。

如何使用 state

使用 state 只需要在 class 當中直接去定義即可,直接去定義一個 state 物件,這個物件代表了元件內的屬性狀態,而我們要使用 state 時候可以透過 this.state 調用,如下面的範例,我們在 UserProfile 這個元件中定義了 state,並設定 userName 屬性,在 JSX 中調用時,如果沒有這個屬性,就判斷返回 null

class UserProfile extends React.Component {
    state = { //定義 state
        userName: Leo
    }
    
    render () {
        return <div>
            { this.state.userName ? this.state.userName : null}
        </div>
    }
}

若是開發項目本身不支援直接定義 class 屬性的方式,就必須使用 constructor 構造函式(官方的 create-react-app 是支援的)。

class UserProfile extends React.Component {
    constructor () {
        super();
        this.state = { //定義 state
            userName: Leo
        }
    }
    
    render () {
        return <div>
            { this.state.userName ? this.state.userName : null}
        </div>
    }
}

運用 setState() 改變 state 狀態

setState 是 class component 本身的一個 function,用於更新元件的狀態。setState 是非同步的,接受兩個參數 setState(stateObject, callback)

  • stateObject: stateObject 必須是一個物件或是函式,物件代表的是 state 更新後的狀態,函式的話是返回一個物件,也是代表更新後的 state。
  • callback: callback 就是執行後的回調函式。

setState() 使用方式

setState() 函式執行的時候,裡面是去塞入要更新的 state 值。以下面的範例來說,我們先在 state 中定義一個 count 屬性,在元件中設定一個 addCount 函式,每次執行這個函式就會去更新 state 中的 count,讓它 +1。我們在 JSX 中呼叫 count,並設置一個 buttononClick 事件去綁定 addCount 函式,這樣每次點擊 button 就會更新 count。我們也可以將更新的函式直接寫在 onClick 中,這樣就不必綁定 this

class App extends React.Component {
    state = {
        count: 0
    }
    
    addCount () {
        const { count } = this.state; //取得 this.state.count 的值,並賦到新的 count 變數上
        this.setState((
            count: count + 1 //將新的 count +1 後賦給 state 中的 count
        ))
    }
    
    
    render () {
        // 在 button 上利用 onClick 綁定 addCount 事件
        const { count } = this.state;
        return <div>
            <div>{ count }</div>
            <button onClick={this.addCount.bind(this)}></button>
            <button onClick={() => {
                    const { count } = this.state;
                    this.setState({
                        count: count + 1
                    })
                }
            }>click</button>
        </div>
    }
}

state 雙向綁定

state 也可以經由 setState() 做雙向綁定,一般是應用在表單輸入方面,例如下面的 input,利用 onChange 來偵測 value 的變化,如果有變化,就用 setState 去更新 state

class App extends React.Component {
    state = {
        value: ''
    }
    
    render () {
        const { value } = this.state;
        return <div>
            <input
                type="text"
                value={ this.state.value }
                onChange={ e => this.setState({ value: e.target.value })}
            />
            <p>輸入的值為:{ value }</p>
        </div>
    }
}

state 的同步更新

前面有提到 state 的更新是非同步的,如下面的範例,兩個 setState() 是同時執行的,所以當我們點擊 button 後,count 會因為加 1 而變為 1,但是 age 則不會變,因為兩個 setState() 是同時執行,所以第二個 setState() 執行時,count還是 0,因此 age 不會改變。

class App extends React.Component {
    state = {
        name: 'leo',
        age: 18,
        count: 0
    }
    
    changeData () {
        const { age, count } = this.state
        //由於是非同步,兩個 setState 會同時執行
        this.setState({ count: count + 1}); //count 變為 1
        this.setState({ age: age + this.state.count}); // 跟上一行同時執行,執行時 count 還是 0,所以 age 數字不變
    }
    
    render () {
        const { name, age, count } = this.state;
        return <div>
            {name} {age} 歲 || {count}
            <button onClick={this.changeData.bind(this)}>change</button>
        </div>
    }
}

但有些情況下我們會一次更新多個 state,這種時候通常會希望一個 state 更新完再去執行下一個 state 的更新。這種時候就會用到前面提過的 setState 的另一個參數 callback。我們在第一個 setState 的回調函數中去執行第二個 setState

class App extends React.Component {
    state = {
        name: 'leo',
        age: 18,
        count: 0
    }
    
    changeData () {
        const { age, count } = this.state
        // 在 callback 中去執行第二個 setState
        this.setState({ count: count + 1}, () => {
            this.setState({ age: age + this.state.count});
        });
    }
    
    render () {
        const { name, age, count } = this.state;
        return <div>
            {name} {age} 歲 || {count}
            <button onClick={this.changeData.bind(this)}>change</button>
        </div>
    }
}

但上面的這種寫法,一但 setState 很多,就會陷入以前常在非同步處理中見到的 callback hell,維護上也不是很直覺。因此有了利用前面章節提過的 async await 來搭配處理的方式,讓我們在開發和維護上都更簡潔易懂。

state = {
        name: 'leo',
        age: 18,
        count: 0
    }
    
    async changeData () {
        const { age, count } = this.state
        await this.setState({ count: count + 1});
        await this.setState({ age: age + this.state.count}); // 上一行執行完才執行這一行
    }
    
    render () {
        const { name, age, count } = this.state;
        return <div>
            {name} {age} 歲 || {count}
            <button onClick={this.changeData.bind(this)}>change</button>
        </div>
    }

我們也可以透過 Promise 搭配 async await 來處理同步及非同步問題。

class UserProfile extends React.Component {
    state = {
        name: 'leo',
        age: 18,
        count: 0
    }
    
    // 先用 Promise 來包裝好 setState()
    changeVlue (state) {
        return new Promise((resolve) => {
            this.setState(state, resolve)
        })
    }

    // 利用 async await 直接調用 Promise
    async changeData () {
        const { age, count } = this.state
        await this.changeVlue({count: count + 1})
        await this.changeVlue({age: age + this.state.count}); // 上一行執行完才執行這一行
    }
    
    render () {
        const { name, age, count } = this.state;
        return <div>
            {name} {age} 歲 || {count}
            <button onClick={this.changeData.bind(this)}>change</button>
        </div>
    }
}

小結

由於 stateprops 部分要注意的比較多,因此我分成兩篇來整理(其實是因為後面文章來不及寫了)。下一篇會整理 props 相關的概念。


上一篇
【Day 9】React 元件寫法
下一篇
【Day 11】元件狀態管理 (二) props & 元件間傳遞參數的方式
系列文
React 30 天學習歷程30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言