摘要:承接上文,深入知識點整理一使用也滿一年了,從剛剛會使用到逐漸探究其底層實現,以便學習幾招奇技淫巧從而在自己的代碼中使用,寫出高效的代碼。有限狀態機,表示有限個狀態以及在這些狀態之間的轉移和動作等行為的模型。
承接上文,深入React知識點整理(一)
使用React也滿一年了,從剛剛會使用到逐漸探究其底層實現,以便學習幾招奇技淫巧從而在自己的代碼中使用,寫出高效的代碼。下面整理一些知識點,算是React看書,使用,感悟的一些總結:
React 生命周期
setState調用棧
7.React 生命周期React 的主要思想是通過構建可復用組件來構建用戶界面。所謂組件,其實就是有限狀態機(FSM),通過狀態渲染對應的界面,且每個組件都有自己的生命周期,它規定了組件的狀態和方法需要在哪個階段改變和執行。有限狀態機,表示有限個狀態以及在這些狀態之間的轉移和動作等行為的模型。一般通過狀態、事件、轉換和動作來描述有限狀態機。
組件的生命周期在不同狀態下的執行順序:
當首次掛載組件時,按順序執行 getDefaultProps、getInitialState、componentWillMount、render 和 componentDidMount。
當卸載組件時,執行 componentWillUnmount。
當重新掛載組件時,此時按順序執行 getInitialState、componentWillMount、render 和 componentDidMount,但并不執行 getDefaultProps。
當再次渲染組件時,組件接受到更新狀態,此時按順序執行 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate。
constructor 中的 this.state = {} 其實就是調用內部的 getInitialState 方法。
自定義組件(ReactCompositeComponent)的生命周期主要通過 3 個階段進行管理——MOUNTING、RECEIVE_PROPS 和 UNMOUNTING,它們負責通知組件當前所處的階段,應該執行生命周期中的哪個步驟。
創建組件:createClass或者extend Component創建自定義組件,初始化defaultProps。
MOUNTING:調用getInitialState初始化state,調用componentWillMount,之后render,最后調用componentDidMount。通過 mountComponent 掛載組件,初始化序號、標記等參數,判斷是否為無狀態組件,并進行對應的組件初始化工作,比如初始化 props、context 等參數。利用 getInitialState 獲取初始化state、初始化更新隊列和更新狀態。
RECEIVE_PROPS:當參數變化的時候,按照圖中順序調用,且在 componentWillReceiveProps、shouldComponentUpdate 和componentWillUpdate中也還是無法獲取到更新后的 this.state,即此時訪問的 this.state 仍然是未更新的數據。updateComponent 本質上也是通過遞歸渲染內容的,由于遞歸的特性,父組件的 componentWillUpdate是在其子組件的 componentWillUpdate 之前調用的,而父組件的 componentDidUpdate也是在其子組件的 componentDidUpdate 之后調用的。
UNMOUNTING:如果存在 componentWillUnmount,則執行并重置所有相關參數、更新隊列以及更新狀態,如果此時在 componentWillUnmount 中調用 setState,是不會觸發 re-render 的,這是因為所有更新
隊列和更新狀態都被重置為 null,并清除了公共類,完成了組件卸載操作。
最后,一張圖歸納React生命周期,以及不同生命周期中setState的使用情況:
屏幕快照 2017-12-14 下午10.42.31.png
這里主要介紹了React生命周期執行順序,以及在不同生命周期內使用setState的情況,重點在于合適使用setState。
8.setState調用棧React中的setState是個很著名的方法,當你需要更改state觸發頁面重繪的時候,調用setSatet方法。但是setState并不會立刻執行你的更改,這也是剛開始使用React時很苦惱的一件事。
經過一段時間的使用和學習,知道了React的setState是"異步"的:
State Updates May Be AsynchronousReact may batch multiple setState() calls into a single update for performance.
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
官方給出的回答是React為了優化性能,會合并多次setState操作,并計算出最終值再執行,所以setState看起來是"異步"的。這種想法很好,我們可以借鑒到項目開發中去,但是代碼層面是如何實現的?在任何地方調用setState都會是’異步的嗎‘?
setState源碼:
// 更新 state ReactComponent.prototype.setState = function (partialState, callback) { this.updater.enqueueSetState(this, partialState); if (callback) { this.updater.enqueueCallback(this, callback, "setState"); } }; ... enqueueSetState: function (publicInstance, partialState) { var internalInstance = getInternalInstanceReadyForUpdate( publicInstance, "setState" ); if (!internalInstance) { return; } // 更新隊列合并操作 var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []); queue.push(partialState); enqueueUpdate(internalInstance); }, ... // 如果存在 _pendingElement、_pendingStateQueue和_pendingForceUpdate,則更新組件 performUpdateIfNecessary: function (transaction) { if (this._pendingElement != null) { ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context); } if (this._pendingStateQueue !== null || this._pendingForceUpdate) { this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context); } }
通過上面源碼大致可以看出,setState函數執行的時候,把被更新的state放入到_pendingStateQuenue隊列中,之后將組件實例傳入并執行enqueueUpdate,并且如果有回調函數放到enqueueCallback中。
問題現在全在enqueueUpdate上,通過簡易的流程圖可以看出enqueueUpdate判斷當前組件是否處于批量更新模式中,是就簡單將組件存入臟組件隊列,不是的話更新臟組件隊列,從而更新調用updateComponent,更新props和state,觸發頁面重繪。
屏幕快照 2017-12-07 下午7.46.18.png
到這里可以看到很清楚地看到,setState并不是全部都是’異步的‘,當組件處于非批量更新模式的時候,是立即更新的。enqueueUpdate源碼:
function enqueueUpdate(component) { ensureInjected(); // 如果不處于批量更新模式 if (!batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component); return; } // 如果處于批量更新模式,則將該組件保存在 dirtyComponents 中 dirtyComponents.push(component); }
batchingStrategy代碼如下:
var ReactDefaultBatchingStrategy = { isBatchingUpdates: false, batchedUpdates: function(callback, a, b, c, d, e) { var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates; ReactDefaultBatchingStrategy.isBatchingUpdates = true; if (alreadyBatchingUpdates) { callback(a, b, c, d, e); } else { transaction.perform(callback, null, a, b, c, d, e); } }, }
通過代碼來看,isBatchingUpdates默認是false,也就是組件默認是不處于批量更新模式的,第一次執行enqueueUpdate時候,在batchedUpdates方法中,將開關置true,之后更新都處于批量更新模式。
這里還有一個概念:事務。
事務就是將需要執行的方法使用 wrapper 封裝起來,再通過事務提供的 perform 方法執行。而在 perform 之前,先執行所有 wrapper 中的 initialize 方法,執行完 perform 之后(即執行method 方法后)再執行所有的 close 方法。一組 initialize 及 close 方法稱為一個 wrapper。從圖3-16中可以看出,事務支持多個 wrapper 疊加。
屏幕快照 2017-12-07 下午8.28.26.png
那事務又跟setState有什么關系,我們舉個例子:
import React, { Component } from "react"; class Example extends Component { constructor() { super(); this.state = { val: 0 }; } componentDidMount() { this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 1 次輸出 this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 2 次輸出 setTimeout(() => { this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 3 次輸出 this.setState({val: this.state.val + 1}); console.log(this.state.val); // 第 4 次輸出 }, 0); } render() { return null; } }
這個例子中,componentDidMount生命周期函數中連著調用了兩次setState,之后在setState中又連著調用了兩次,結果卻完全不同,打印輸出0,0,2,3。嘗試著在React的各個階段函數中打印log結果如下:
屏幕快照 2017-12-07 下午9.20.54.png
通過截圖可以看到,我們在調用setState的之后,打印輸出了事務結束的closeAll,這說明componentDidMount函數被事務當做method調用,之后才打印輸出batchedUpdates。原來早在 setState 調用前,已經處于batchedUpdates 執行的事務中了。那這次 batchedUpdate 方法,又是誰調用的呢?讓我們往前再追溯一層,原來是 ReactMount.js中的 _renderNewRootComponent 方法。也就是說,整個將 React 組件渲染到 DOM 中的過程就處于一個大的事務中。
接下來的解釋就順理成章了,因為在 componentDidMount 中調用 setState 時,batchingStrategy的 isBatchingUpdates 已經被設為 true,所以兩次 setState 的結果并沒有立即生效,而是被放進了 dirtyComponents 中。這也解釋了兩次打印 this.state.val 都是 0 的原因,因為新的 state 還沒有被應用到組件中。而setTimeout函數中的setState并沒有在事務中,所以立即執行。
所以這里得到一個結論:在React中,如果是由React引發的事件處理(比如通過onClick引發的事件處理),調用setState不會同步更新this.state,除此之外的setState調用會同步執行this.state。
React引發的事件處理有很多,都是我們常用的比如所有的生命周期函數(在生命周期函數中可以使用setState的),React代理的事件;這些函數應用場景很高,讓我們誤以為setState一直是"異步"的。
所以,我們也可以得出解決setState異步的方法:
使用setState第二個參數callback;
setTimeout函數(雖然不好,但也不失為一種辦法);
在componentDidUpdate處理一些邏輯(要看應用場景);
第一個參數傳入計算函數;
當然了,還有一個比較決絕的辦法,就是不用setState。
相關文章可以看看程墨老師的文章:setState為什么不會同步更新組件狀態。
總結React算是一個顛覆式的UI框架,有很多知識點值得學習,深入的研究一些問題對于實際使用上也會有一定幫助。本文也是根據《深入React技術棧》前幾章內容摘錄,整理的知識點,有興趣可以看看原著。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/92139.html
showImg(https://segmentfault.com/img/remote/1460000018716142?w=200&h=200); showImg(https://segmentfault.com/img/remote/1460000018716143);showImg(https://segmentfault.com/img/remote/1460000010953710);...
摘要:因為工作中一直在使用,也一直以來想總結一下自己關于的一些知識經驗。于是把一些想法慢慢整理書寫下來,做成一本開源免費專業簡單的入門級別的小書,提供給社區。本書的后續可能會做成視頻版本,敬請期待。本作品采用署名禁止演繹國際許可協議進行許可 React.js 小書 本文作者:胡子大哈本文原文:React.js 小書 轉載請注明出處,保留原文鏈接以及作者信息 在線閱讀:http://huzi...
摘要:以我自己的理解,函數式編程就是以函數為中心,將大段過程拆成一個個函數,組合嵌套使用。越來越多的跡象表明,函數式編程已經不再是學術界的最愛,開始大踏步地在業界投入實用。也許繼面向對象編程之后,函數式編程會成為下一個編程的主流范式。 使用React也滿一年了,從剛剛會使用到逐漸探究其底層實現,以便學習幾招奇技淫巧從而在自己的代碼中使用,寫出高效的代碼。下面整理一些知識點,算是React看書...
摘要:特意對前端學習資源做一個匯總,方便自己學習查閱參考,和好友們共同進步。 特意對前端學習資源做一個匯總,方便自己學習查閱參考,和好友們共同進步。 本以為自己收藏的站點多,可以很快搞定,沒想到一入匯總深似海。還有很多不足&遺漏的地方,歡迎補充。有錯誤的地方,還請斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應和斧正,會及時更新,平時業務工作時也會不定期更...
閱讀 2843·2021-11-19 09:40
閱讀 3701·2021-11-15 18:10
閱讀 3286·2021-11-11 16:55
閱讀 1236·2021-09-28 09:36
閱讀 1654·2021-09-22 15:52
閱讀 3372·2019-08-30 14:06
閱讀 1167·2019-08-29 13:29
閱讀 2312·2019-08-26 17:04