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