如何使用代碼
安裝項目前置依賴,以及啟動項目的方法,參看:React 拾遺:項目腳手架。
請根據文章內容,把相應部分的代碼注釋取消,即可運行。
摘要
本文介紹
- setState() 的異步性
- setState 的回調函數
- setState 獲取之前的狀態
項目代碼地址:React 拾遺:類作為組件 (3)
setState 的異步性
請注意:setState() 可能是異步的。
假設我們需要實現:點擊標題,文字從 'Hello' 改變成 'changed'。在改變前后,把 state 中的 title 打印出來。改變之前應該是 'Hello',改變之后應該是 'change'。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
state = {
title: 'Hello'
};
changeTitle = () => {
console.log(this.state.title);
this.setState({ title: 'changed' })
console.log(this.state.title);
}
render() {
return (
<div>
<h1 onClick={this.changeTitle}>Title - {this.state.title}</h1>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
點擊標題觸發 setState,獲得打印結果,全部都是之前的狀態,第二個不是改變后的狀態。
Hello
Hello
>
setState() 不是馬上就去改變狀態。
原因是:React 中狀態 state 如果被更改,會觸發重新渲染,而渲染是代價很高的動作。假設 setState 是一個同步動作,改變狀態后觸發渲染,可能會導致瀏覽器沒有響應。為了性能考慮,狀態改變之前與之后有一個過渡狀態,setState 會把所有的改變狀態的請求匯總、形成一個隊列,React 會根據隊列去執行狀態改變的任務。所以 setState 類似于一個 http 請求(request),并不是發起請求以后,馬上就有請求的結果。setState 負責歸并請求、形成隊列,React 會根據情況修改狀態、觸發渲染。
重點是,setState() 改變狀態,然后馬上去獲取改變后的狀態,并不能可能還是之前的狀態。
setState 的回調函數
如果一定要 setState 之后,獲取到狀態改變之后的數據,有兩種方法:
- 使用 setState 的回調函數
- 使用生命周期 componentDidUpdate 函數
setState 的回調函數是可選項。
setState(stateChange[, callback])
把之前的代碼中加入回調函數
changeTitle = () => {
console.log(this.state.title);
this.setState({ title: 'changed' }, () => {
console.log(this.state.title)
})
console.log(this.state.title);
}
再次點擊標題觸發 setState,獲得打印結果會得到。最后 'changed' 就是 setState 的回調函數得到的結果。
Hello
Hello
changed
>
setState 獲取之前的狀態
如果 setState 不依賴之前的狀態,就是直接修改,比如之前使用的代碼:
setState({ title: 'changed' })
但是如果 setState 依賴之前的狀態,就可以寫成如下類似的代碼:
setState({ num: this.state.num +1 })
完整代碼是
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
state = {
num: 0
};
changeTitle = () => {
this.setState({
num: this.state.num + 1
});
};
render() {
return (
<div>
<h1>Count: {this.state.num}</h1>
<button onClick={this.changeTitle}>Increment</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
但是,這個 this.state.<someVariable>
是一個不穩定的存在。因為 setState 形成了一個狀態改變請求的隊列。本次修改使用的 this.state.<someVariable>
可能并不是理想的前次狀態的結果。
如果狀態修改依賴之前的狀態數據,最佳實踐是使用更新函數。
- 在
this.setState()
中傳入的參數不再是一個對象,而是一個更新函數(updater function)。 - 更新函數第一個參數就是之前的狀態,第二個函數是 props。
- 用之前的狀態
prevState
來替代this.state
是最佳實踐
this.setState((prevState, props) => {
return { num: prevState.counter + 1 };
});
完整的修改函數是:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class App extends Component {
state = {
num: 0
};
changeTitle = () => {
this.setState((prevState, props) => {
return { num: prevState.num + 1 }
});
};
render() {
return (
<div>
<h1>Count: {this.state.num}</h1>
<button onClick={this.changeTitle}>Increment</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
另外提一點,之前使用返回對象的形式。
this.setState({ title: 'changed' })
即使使用不帶參數的箭頭函數,也可以實現。據稱后面這種箭頭函數的形式,渲染性能更佳。
this.setState(() => { title: 'changed' })