React中setState同步更新策略從屬於筆者的Web 前端入門與工程實踐中的React入門與最佳實踐系列總綱系列文章,推薦閱讀2016-個人前端之路:工具化與工程化。前端
咱們在上文中說起,爲了提升性能React將setState設置爲批次更新,便是異步操做函數,並不能以順序控制流的方式設置某些事件,咱們也不能依賴於this.state
來計算將來狀態。典型的譬如咱們但願在從服務端抓取數據而且渲染到界面以後,再隱藏加載進度條或者外部加載提示:git
componentDidMount() { fetch('https://example.com') .then((res) => res.json()) .then( (something) => { this.setState({ something }); StatusBar.setNetworkActivityIndicatorVisible(false); } ); }
由於setState
函數並不會阻塞等待狀態更新完畢,所以setNetworkActivityIndicatorVisible
有可能先於數據渲染完畢就執行。咱們能夠選擇在componentWillUpdate
與componentDidUpdate
這兩個生命週期的回調函數中執行setNetworkActivityIndicatorVisible
,可是會讓代碼變得破碎,可讀性也很差。實際上在項目開發中咱們更頻繁碰見此類問題的場景是以某個變量控制元素可見性:github
this.setState({showForm : !this.state.showForm});
咱們預期的效果是每次事件觸發後改變表單的可見性,可是在大型應用程序中若是事件的觸發速度快於setState
的更新速度,那麼咱們的值計算徹底就是錯的。本節就是討論兩種方式來保證setState
的同步更新。編程
setState
函數的第二個參數容許傳入回調函數,在狀態更新完畢後進行調用,譬如:json
this.setState({ load: !this.state.load, count: this.state.count + 1 }, () => { console.log(this.state.count); console.log('加載完成') });
這裏的回調函數用法相信你們很熟悉,就是JavaScript異步編程相關知識,咱們能夠引入Promise來封裝setState:api
setStateAsync(state) { return new Promise((resolve) => { this.setState(state, resolve) }); }
setStateAsync
返回的是Promise對象,在調用時咱們可使用Async/Await語法來優化代碼風格:網絡
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
渲染完畢以後調用外部狀態欄將網絡請求狀態修改成已結束,整個組件的完整定義爲:異步
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> ); } }
該組件的執行效果以下所示:async
除了使用回調函數的方式監聽狀態更新結果以外,React還容許咱們傳入某個狀態計算函數而不是對象來做爲第一個參數。狀態計算函數可以爲咱們提供可信賴的組件的State與Props值,即會自動地將咱們的狀態更新操做添加到隊列中並等待前面的更新完畢後傳入最新的狀態值:異步編程
this.setState(function(prevState, props){ return {showForm: !prevState.showForm} });
這裏咱們以簡單的計數器爲例,咱們但願用戶點擊按鈕以後將計數值連加兩次,基本的組件爲:
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> } }
直觀的寫法咱們能夠連續調用兩次setState
函數,這邊的用法可能看起來有點怪異,不過更多的是爲了說明異步更新帶來的數據不可預測問題。
incrementCount(){ this.setState({count : this.state.count + 1}) this.setState({count : this.state.count + 1}) }
上述代碼的效果是每次點擊以後計數值只會加1,實際上第二個setState
並無等待第一個setState
執行完畢就開始執行了,所以其依賴的當前計數值徹底是錯的。咱們固然可使用上文說起的setStateAsync
來進行同步控制,不過這裏咱們使用狀態計算函數來保證同步性:
incrementCount(){ this.setState((prevState, props) => ({ count: prevState.count + 1 })); this.setState((prevState, props) => ({ count: prevState.count + 1 })); }
這裏的第二個setState
傳入的prevState
值就是第一個setState
執行完畢以後的計數值,也順利保證了連續自增兩次。