摘要:判斷當前是否處于批量更新狀態,如果是,將當前組件加入待更新的組件隊列中。將組件的暫存隊列中的進行合并,獲得最終要更新的對象,并將隊列置為空。執行生命周期,根據返回值判斷是否要繼續更新。
this.setState( )方法是React.js中最常見的一種方法,利用它可以控制各種狀態變化,達到頁面各種交互效果,但是,我們在React開發中偶爾會發現,明明已經通過this.setState( )方法處理過某個state的值,但是在后續的方法里,log打印出來仍然是之前的值,或者,第一次獲取到原來的值,第二次才能獲取到設置之后的新值,讓人誤以為是因為電腦或瀏覽器性能問題造成的"延遲"問題。
執行過程為了理解這個問題,我們首先來看一下setState這個過程中發生了什么:
將setState傳入的partialState參數存儲在當前組件實例的state暫存隊列中。
判斷當前React是否處于批量更新狀態,如果是,將當前組件加入待更新的組件隊列中。
如果未處于批量更新狀態,將批量更新狀態標識設置為true,用事務再次調用前一步方法,保證當前組件加入到了待更新組件隊列中。
調用事務的waper方法,遍歷待更新組件隊列依次執行更新。
執行生命周期componentWillReceiveProps。
將組件的state暫存隊列中的state進行合并,獲得最終要更新的state對象,并將隊列置為空。
執行生命周期componentShouldUpdate,根據返回值判斷是否要繼續更新。
執行生命周期componentWillUpdate。
執行真正的更新,render重新渲染。
執行生命周期componentDidUpdate。
官方解釋首先思考為什么會出現這種情況,在facebook給出的官方文檔中我們可以看到這么一段話:
setState(updater[, callback])
Think of setState( ) as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
setState( ) does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState( ) a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
總結以下,就是以下幾點:
setState( ) 更類似于是一種請求而不是立即更新組件的命令
為了更好的性能,React會延遲調用它,不會保證state的變更會立即生效,而是會批量推遲更新
官方承認會存在隱患
建議在componentDidUpdate中執行或利用回調函數(setState(updater, callback))
舉個簡單例子:
constructor(props) { super(props); this.state = { num: 1 }; } componentDidMount = () => { this.setState({ num: this.state.num + 1 }); console.log(this.state.num); // 1 }
這是因為this.setState( )本身是異步的,程序異步運行,可以提高程序運行的效率,不必等一個程序跑完,再跑下一個程序,特別當這兩個程序是無關的時候。React會去合并所有的state變化,在前一個方法未執行完時,就先開始運行后一個方法。但是實際操作中,為了能實時獲取后一個狀態值,需要一些解決的辦法。
利用全局屬性嘗試一下換個寫法,利用全局屬性的辦法而不是用state的方式去獲取數據:
constructor(props) { super(props); this.num = 1; } componentDidMount = () => { this.num = this.num + 1; console.log(this.num); // 2 }
這其實是一種取巧的方式,寫法方便,原理簡單,但是并不十分推薦,因為它并不符合React中關于有狀態組件的設計理念,存在有可能無法觸發刷新的風險(雖然在我的開發過程從沒有發生這樣的事),所以還是希望大家優先使用下面的方法。
利用回調函數回調函數眾所周知,就是某個函數執行完畢后執行的函數,利用它可以確保在this.setState( )整個函數執行完成之后去獲取this.state.xxx的值:
constructor(props) { super(props); this.state = { num: 1 }; } componentDidMount = () => { this.setState({ num: this.state.num + 1 }, () => { console.log(this.state.num); // 2 }); console.log(this.state.num); // 1 }
控制臺按順序先后打印出兩個結果:
1 2利用setTimeout( )
首先簡單回顧一下,利用setTimeout( )模擬一下前文提到的Javascript中的異步:
foo = () => { console.log("11111111"); setTimeout(function(){ console.log("22222222"); },1000); }; bar = () => { console.log("33333333"); } foo(); bar(); // 11111111 // 33333333 // 22222222
所以,在上述代碼塊中,在前一方法(foo)執行時,后一方法(bar)也可以執行。符合異步的基本概念,程序并不按順序執行。在foo函數中執行到setTimeout的時候,函數會跳出,并先執行bar( )方法,這樣就模擬了一個異步的效果。這里順便再提一下前面說的,setState方法通過一個隊列機制實現state更新,當執行setState的時候,會將需要更新的state合并之后放入狀態隊列,而不會立即更新,通過下面的例子可見。
constructor(props) { super(props); this.state = { num: 1, }; } componentWillMount = () => { this.setState({ num: this.state.num + 1, }); console.log(this.state.num); this.setState({ num: this.state.num + 1, }); console.log(this.state.num); } render() { console.log(this.state.num); return (); }
代碼輸出結果為 1,1,2
利用setTimeout方法可以解決state的異步問題,因為setState只在合成事件和鉤子函數中是“異步”的,在原生事件和setTimeout 中都是同步的:
componentWillMount = () => { setTimeout(() => { this.setState({ num: this.state.num + 1, }); console.log(this.state.num); // 1 this.setState({ num: this.state.num + 1, }); console.log(this.state.num); // 2 }, 0); }利用componentDidUpdate( )
根據前面文檔所說,在componentDidUpdate( )方法中去獲取新的state值,根據React的生命周期,此時this.state已經更新。
constructor(props) { super(props); this.state = { num: 1 }; } componentWillMount = () => { this.setState({ num: this.state.num + 1 }); } componentDidUpdate = () => { console.log(this.state.num); // 2 }警告
??注意,很多新人在遇到這種問題時無所適從,可能會用一些投機取巧的方式,方面的全局對象是一種方式,還有一種就是繞過setState直接賦值:
this.state.num = 2 // 2
理論上講,這種方法當然也能達到賦值目的,但將state設計成更新延緩到最后批量合并再去渲染,對于應用的性能優化是有極大好處的,如果每次的狀態改變都去重新渲染真實dom,那么它將帶來巨大的性能消耗,所以不建議上面寫法。
??如果在shouldComponentUpdate或者componentWillUpdate方法中調用setState,此時this._pending-StateQueue != null,就會造成循環調用,使得瀏覽器內存占滿后崩潰。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109957.html
摘要:正文在回復中表示為什么是異步的,這并沒有一個明顯的答案,每種方案都有它的權衡。需要注意的是,異步更新是有可能實現這種設想的前提。 前言 不知道大家有沒有過這個疑問,React 中 setState() 為什么是異步的?我一度認為 setState() 是同步的,知道它是異步的之后很是困惑,甚至期待 React 能出一個 setStateSync() 之類的 API。同樣有此疑問的還有 ...
摘要:不保證這個狀態的更新是立即執行的。這個問題導致如果開發者在之后立即去訪問可能訪問的不是最新的狀態。不應該被直接更改,而是應該新建一個來表示更新后的狀態。實驗采用基于控制變量法的對照試驗。至于的問題,留給讀者自己吧。 React組件重新渲染的條件是: B.只要調用this.setState()就會發生重新渲染。 C.必須調用this.setState()且傳遞不同于當前this.setS...
摘要:項目簡介本次使用了和開發了一個地址輸入框,主要實現的功能有限制輸入符合條件的字符并每隔兩位可以自動添加用于分割的冒號。項目屏蔽了的事件處理,同時使用來手動控制光標。繼承于和因此同時具有和兩者的方法。后面的和都是需要利用最新的來進行判斷的。 項目簡介 本次使用了RxJS和react開發了一個mac地址輸入框,主要實現的功能有限制輸入符合條件的字符1-9,a-f,并每隔兩位可以自動添加用于...
摘要:異步渲染利用事件循環,延遲渲染函數的調用調用回調函數處理后跟函數的情況淺合并邏輯事件循環,關于的事件循環和的事件循環后續會單獨寫篇文章。 showImg(https://segmentfault.com/img/remote/1460000015785464?w=640&h=280); 看源碼一個痛處是會陷進理不順主干的困局中,本系列文章在實現一個 (x)react 的同時理順 Rea...
摘要:處理事件響應是應用中非常重要的一部分。中,處理事件響應的方式有多種。關于事件響應的回調函數,還有一個地方需要注意。不管你在回調函數中有沒有顯式的聲明事件參數,都會把事件作為參數傳遞給回調函數,且參數的位置總是在其他自定義參數的后面。 React中定義一個組件,可以通過React.createClass或者ES6的class。本文討論的React組件是基于class定義的組件。采用cla...
閱讀 3881·2021-11-24 11:14
閱讀 3321·2021-11-22 13:53
閱讀 3883·2021-11-11 16:54
閱讀 1546·2021-10-13 09:49
閱讀 1211·2021-10-08 10:05
閱讀 3392·2021-09-22 15:57
閱讀 1754·2021-08-16 11:01
閱讀 965·2019-08-30 15:55