React中setState同步更新策略
setState 同步更新
我們在上文中提及,為了提高性能React將setState設置為批次更新,即是異步操作函數(shù),并不能以順序控制流的方式設置某些事件,我們也不能依賴于this.state來計算未來狀態(tài)。典型的譬如我們希望在從服務端抓取數(shù)據(jù)并且渲染到界面之后,再隱藏加載進度條或者外部加載提示:
- componentDidMount() {
- fetch('https://example.com')
- .then((res) => res.json())
- .then(
- (something) => {
- this.setState({ something });
- StatusBar.setNetworkActivityIndicatorVisible(false);
- }
- );
- }
因為setState函數(shù)并不會阻塞等待狀態(tài)更新完畢,因此setNetworkActivityIndicatorVisible有可能先于數(shù)據(jù)渲染完畢就執(zhí)行。我們可以選擇在componentWillUpdate與componentDidUpdate這兩個生命周期的回調函數(shù)中執(zhí)行setNetworkActivityIndicatorVisible,但是會讓代碼變得破碎,可讀性也不好。實際上在項目開發(fā)中我們更頻繁遇見此類問題的場景是以某個變量控制元素可見性:
- this.setState({showForm : !this.state.showForm});
我們預期的效果是每次事件觸發(fā)后改變表單的可見性,但是在大型應用程序中如果事件的觸發(fā)速度快于setState的更新速度,那么我們的值計算完全就是錯的。本節(jié)就是討論兩種方式來保證setState的同步更新。
完成回調
setState函數(shù)的第二個參數(shù)允許傳入回調函數(shù),在狀態(tài)更新完畢后進行調用,譬如:
- this.setState({
- load: !this.state.load,
- count: this.state.count + 1
- }, () => {
- console.log(this.state.count);
- console.log('加載完成')
- });
這里的回調函數(shù)用法相信大家很熟悉,就是JavaScript異步編程相關知識,我們可以引入Promise來封裝setState:
- setStateAsync(state) {
- return new Promise((resolve) => {
- this.setState(state, resolve)
- });
- }
setStateAsync 返回的是Promise對象,在調用時我們可以使用Async/Await語法來優(yōu)化代碼風格:
- async componentDidMount() {
- StatusBar.setNetworkActivityIndicatorVisible(true)
- const res = await fetch('https://api.ipify.org?format=json')
- const {ip} = await res.json()
- await this.setStateAsync({ipAddress: ip})
- StatusBar.setNetworkActivityIndicatorVisible(false)
- }
這里我們就可以保證在setState渲染完畢之后調用外部狀態(tài)欄將網(wǎng)絡請求狀態(tài)修改為已結束,整個組件的完整定義為:
- class AwesomeProject extends Component {
- state = {}
- setStateAsync(state) {
- ...
- }
- async componentDidMount() {
- ...
- }
- render() {
- return (
- <View style={styles.container}>
- <Text style={styles.welcome}>
- My IP is {this.state.ipAddress || 'Unknown'}
- </Text>
- </View>
- );
- }
- }
傳入狀態(tài)計算函數(shù)
除了使用回調函數(shù)的方式監(jiān)聽狀態(tài)更新結果之外,React還允許我們傳入某個狀態(tài)計算函數(shù)而不是對象來作為***個參數(shù)。狀態(tài)計算函數(shù)能夠為我們提供可信賴的組件的State與Props值,即會自動地將我們的狀態(tài)更新操作添加到隊列中并等待前面的更新完畢后傳入***的狀態(tài)值:
- this.setState(function(prevState, props){
- return {showForm: !prevState.showForm}
- });
這里我們以簡單的計數(shù)器為例,我們希望用戶點擊按鈕之后將計數(shù)值連加兩次,基本的組件為:
- class Counter extends React.Component{
- constructor(props){
- super(props);
- this.state = {count : 0}
- this.incrementCount = this.incrementCount.bind(this)
- }
- incrementCount(){
- ...
- }
- render(){
- return <div>
- <button onClick={this.incrementCount}>Increment</button>
- <div>{this.state.count}</div>
- </div>
- }
- }
直觀的寫法我們可以連續(xù)調用兩次setState函數(shù),這邊的用法可能看起來有點怪異,不過更多的是為了說明異步更新帶來的數(shù)據(jù)不可預測問題。
- incrementCount(){
- this.setState({count : this.state.count + 1})
- this.setState({count : this.state.count + 1})
- }
上述代碼的效果是每次點擊之后計數(shù)值只會加1,實際上第二個setState并沒有等待***個setState執(zhí)行完畢就開始執(zhí)行了,因此其依賴的當前計數(shù)值完全是錯的。我們當然可以使用上文提及的setStateAsync來進行同步控制,不過這里我們使用狀態(tài)計算函數(shù)來保證同步性:
- incrementCount(){
- this.setState((prevState, props) => ({
- count: prevState.count + 1
- }));
- this.setState((prevState, props) => ({
- count: prevState.count + 1
- }));
- }
這里的第二個setState傳入的prevState值就是***個setState執(zhí)行完畢之后的計數(shù)值,也順利保證了連續(xù)自增兩次。
【本文是51CTO專欄作者“張梓雄 ”的原創(chuàng)文章,如需轉載請通過51CTO與作者聯(lián)系】