摘要:承接上文,深入知識(shí)點(diǎn)整理一使用也滿(mǎn)一年了,從剛剛會(huì)使用到逐漸探究其底層實(shí)現(xiàn),以便學(xué)習(xí)幾招奇技淫巧從而在自己的代碼中使用,寫(xiě)出高效的代碼。有限狀態(tài)機(jī),表示有限個(gè)狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動(dòng)作等行為的模型。
承接上文,深入React知識(shí)點(diǎn)整理(一)
使用React也滿(mǎn)一年了,從剛剛會(huì)使用到逐漸探究其底層實(shí)現(xiàn),以便學(xué)習(xí)幾招奇技淫巧從而在自己的代碼中使用,寫(xiě)出高效的代碼。下面整理一些知識(shí)點(diǎn),算是React看書(shū),使用,感悟的一些總結(jié):
React 生命周期
setState調(diào)用棧
7.React 生命周期React 的主要思想是通過(guò)構(gòu)建可復(fù)用組件來(lái)構(gòu)建用戶(hù)界面。所謂組件,其實(shí)就是有限狀態(tài)機(jī)(FSM),通過(guò)狀態(tài)渲染對(duì)應(yīng)的界面,且每個(gè)組件都有自己的生命周期,它規(guī)定了組件的狀態(tài)和方法需要在哪個(gè)階段改變和執(zhí)行。有限狀態(tài)機(jī),表示有限個(gè)狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動(dòng)作等行為的模型。一般通過(guò)狀態(tài)、事件、轉(zhuǎn)換和動(dòng)作來(lái)描述有限狀態(tài)機(jī)。
組件的生命周期在不同狀態(tài)下的執(zhí)行順序:
當(dāng)首次掛載組件時(shí),按順序執(zhí)行 getDefaultProps、getInitialState、componentWillMount、render 和 componentDidMount。
當(dāng)卸載組件時(shí),執(zhí)行 componentWillUnmount。
當(dāng)重新掛載組件時(shí),此時(shí)按順序執(zhí)行 getInitialState、componentWillMount、render 和 componentDidMount,但并不執(zhí)行 getDefaultProps。
當(dāng)再次渲染組件時(shí),組件接受到更新?tīng)顟B(tài),此時(shí)按順序執(zhí)行 componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate、render 和 componentDidUpdate。
constructor 中的 this.state = {} 其實(shí)就是調(diào)用內(nèi)部的 getInitialState 方法。
自定義組件(ReactCompositeComponent)的生命周期主要通過(guò) 3 個(gè)階段進(jìn)行管理——MOUNTING、RECEIVE_PROPS 和 UNMOUNTING,它們負(fù)責(zé)通知組件當(dāng)前所處的階段,應(yīng)該執(zhí)行生命周期中的哪個(gè)步驟。
創(chuàng)建組件:createClass或者extend Component創(chuàng)建自定義組件,初始化defaultProps。
MOUNTING:調(diào)用getInitialState初始化state,調(diào)用componentWillMount,之后render,最后調(diào)用componentDidMount。通過(guò) mountComponent 掛載組件,初始化序號(hào)、標(biāo)記等參數(shù),判斷是否為無(wú)狀態(tài)組件,并進(jìn)行對(duì)應(yīng)的組件初始化工作,比如初始化 props、context 等參數(shù)。利用 getInitialState 獲取初始化state、初始化更新隊(duì)列和更新?tīng)顟B(tài)。
RECEIVE_PROPS:當(dāng)參數(shù)變化的時(shí)候,按照?qǐng)D中順序調(diào)用,且在 componentWillReceiveProps、shouldComponentUpdate 和componentWillUpdate中也還是無(wú)法獲取到更新后的 this.state,即此時(shí)訪(fǎng)問(wèn)的 this.state 仍然是未更新的數(shù)據(jù)。updateComponent 本質(zhì)上也是通過(guò)遞歸渲染內(nèi)容的,由于遞歸的特性,父組件的 componentWillUpdate是在其子組件的 componentWillUpdate 之前調(diào)用的,而父組件的 componentDidUpdate也是在其子組件的 componentDidUpdate 之后調(diào)用的。
UNMOUNTING:如果存在 componentWillUnmount,則執(zhí)行并重置所有相關(guān)參數(shù)、更新隊(duì)列以及更新?tīng)顟B(tài),如果此時(shí)在 componentWillUnmount 中調(diào)用 setState,是不會(huì)觸發(fā) re-render 的,這是因?yàn)樗懈?br>隊(duì)列和更新?tīng)顟B(tài)都被重置為 null,并清除了公共類(lèi),完成了組件卸載操作。
最后,一張圖歸納React生命周期,以及不同生命周期中setState的使用情況:
屏幕快照 2017-12-14 下午10.42.31.png
這里主要介紹了React生命周期執(zhí)行順序,以及在不同生命周期內(nèi)使用setState的情況,重點(diǎn)在于合適使用setState。
8.setState調(diào)用棧React中的setState是個(gè)很著名的方法,當(dāng)你需要更改state觸發(fā)頁(yè)面重繪的時(shí)候,調(diào)用setSatet方法。但是setState并不會(huì)立刻執(zhí)行你的更改,這也是剛開(kāi)始使用React時(shí)很苦惱的一件事。
經(jīng)過(guò)一段時(shí)間的使用和學(xué)習(xí),知道了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為了優(yōu)化性能,會(huì)合并多次setState操作,并計(jì)算出最終值再執(zhí)行,所以setState看起來(lái)是"異步"的。這種想法很好,我們可以借鑒到項(xiàng)目開(kāi)發(fā)中去,但是代碼層面是如何實(shí)現(xiàn)的?在任何地方調(diào)用setState都會(huì)是’異步的嗎‘?
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; } // 更新隊(duì)列合并操作 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); } }
通過(guò)上面源碼大致可以看出,setState函數(shù)執(zhí)行的時(shí)候,把被更新的state放入到_pendingStateQuenue隊(duì)列中,之后將組件實(shí)例傳入并執(zhí)行enqueueUpdate,并且如果有回調(diào)函數(shù)放到enqueueCallback中。
問(wèn)題現(xiàn)在全在enqueueUpdate上,通過(guò)簡(jiǎn)易的流程圖可以看出enqueueUpdate判斷當(dāng)前組件是否處于批量更新模式中,是就簡(jiǎn)單將組件存入臟組件隊(duì)列,不是的話(huà)更新臟組件隊(duì)列,從而更新調(diào)用updateComponent,更新props和state,觸發(fā)頁(yè)面重繪。
屏幕快照 2017-12-07 下午7.46.18.png
到這里可以看到很清楚地看到,setState并不是全部都是’異步的‘,當(dāng)組件處于非批量更新模式的時(shí)候,是立即更新的。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); } }, }
通過(guò)代碼來(lái)看,isBatchingUpdates默認(rèn)是false,也就是組件默認(rèn)是不處于批量更新模式的,第一次執(zhí)行enqueueUpdate時(shí)候,在batchedUpdates方法中,將開(kāi)關(guān)置true,之后更新都處于批量更新模式。
這里還有一個(gè)概念:事務(wù)。
事務(wù)就是將需要執(zhí)行的方法使用 wrapper 封裝起來(lái),再通過(guò)事務(wù)提供的 perform 方法執(zhí)行。而在 perform 之前,先執(zhí)行所有 wrapper 中的 initialize 方法,執(zhí)行完 perform 之后(即執(zhí)行method 方法后)再執(zhí)行所有的 close 方法。一組 initialize 及 close 方法稱(chēng)為一個(gè) wrapper。從圖3-16中可以看出,事務(wù)支持多個(gè) wrapper 疊加。
屏幕快照 2017-12-07 下午8.28.26.png
那事務(wù)又跟setState有什么關(guān)系,我們舉個(gè)例子:
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; } }
這個(gè)例子中,componentDidMount生命周期函數(shù)中連著調(diào)用了兩次setState,之后在setState中又連著調(diào)用了兩次,結(jié)果卻完全不同,打印輸出0,0,2,3。嘗試著在React的各個(gè)階段函數(shù)中打印log結(jié)果如下:
屏幕快照 2017-12-07 下午9.20.54.png
通過(guò)截圖可以看到,我們?cè)谡{(diào)用setState的之后,打印輸出了事務(wù)結(jié)束的closeAll,這說(shuō)明componentDidMount函數(shù)被事務(wù)當(dāng)做method調(diào)用,之后才打印輸出batchedUpdates。原來(lái)早在 setState 調(diào)用前,已經(jīng)處于batchedUpdates 執(zhí)行的事務(wù)中了。那這次 batchedUpdate 方法,又是誰(shuí)調(diào)用的呢?讓我們往前再追溯一層,原來(lái)是 ReactMount.js中的 _renderNewRootComponent 方法。也就是說(shuō),整個(gè)將 React 組件渲染到 DOM 中的過(guò)程就處于一個(gè)大的事務(wù)中。
接下來(lái)的解釋就順理成章了,因?yàn)樵?componentDidMount 中調(diào)用 setState 時(shí),batchingStrategy的 isBatchingUpdates 已經(jīng)被設(shè)為 true,所以?xún)纱?setState 的結(jié)果并沒(méi)有立即生效,而是被放進(jìn)了 dirtyComponents 中。這也解釋了兩次打印 this.state.val 都是 0 的原因,因?yàn)樾碌?state 還沒(méi)有被應(yīng)用到組件中。而setTimeout函數(shù)中的setState并沒(méi)有在事務(wù)中,所以立即執(zhí)行。
所以這里得到一個(gè)結(jié)論:在React中,如果是由React引發(fā)的事件處理(比如通過(guò)onClick引發(fā)的事件處理),調(diào)用setState不會(huì)同步更新this.state,除此之外的setState調(diào)用會(huì)同步執(zhí)行this.state。
React引發(fā)的事件處理有很多,都是我們常用的比如所有的生命周期函數(shù)(在生命周期函數(shù)中可以使用setState的),React代理的事件;這些函數(shù)應(yīng)用場(chǎng)景很高,讓我們誤以為setState一直是"異步"的。
所以,我們也可以得出解決setState異步的方法:
使用setState第二個(gè)參數(shù)callback;
setTimeout函數(shù)(雖然不好,但也不失為一種辦法);
在componentDidUpdate處理一些邏輯(要看應(yīng)用場(chǎng)景);
第一個(gè)參數(shù)傳入計(jì)算函數(shù);
當(dāng)然了,還有一個(gè)比較決絕的辦法,就是不用setState。
相關(guān)文章可以看看程墨老師的文章:setState為什么不會(huì)同步更新組件狀態(tài)。
總結(jié)React算是一個(gè)顛覆式的UI框架,有很多知識(shí)點(diǎn)值得學(xué)習(xí),深入的研究一些問(wèn)題對(duì)于實(shí)際使用上也會(huì)有一定幫助。本文也是根據(jù)《深入React技術(shù)?!非皫渍聝?nèi)容摘錄,整理的知識(shí)點(diǎn),有興趣可以看看原著。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/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);...
摘要:因?yàn)楣ぷ髦幸恢痹谑褂?,也一直以?lái)想總結(jié)一下自己關(guān)于的一些知識(shí)經(jīng)驗(yàn)。于是把一些想法慢慢整理書(shū)寫(xiě)下來(lái),做成一本開(kāi)源免費(fèi)專(zhuān)業(yè)簡(jiǎn)單的入門(mén)級(jí)別的小書(shū),提供給社區(qū)。本書(shū)的后續(xù)可能會(huì)做成視頻版本,敬請(qǐng)期待。本作品采用署名禁止演繹國(guó)際許可協(xié)議進(jìn)行許可 React.js 小書(shū) 本文作者:胡子大哈本文原文:React.js 小書(shū) 轉(zhuǎn)載請(qǐng)注明出處,保留原文鏈接以及作者信息 在線(xiàn)閱讀:http://huzi...
摘要:以我自己的理解,函數(shù)式編程就是以函數(shù)為中心,將大段過(guò)程拆成一個(gè)個(gè)函數(shù),組合嵌套使用。越來(lái)越多的跡象表明,函數(shù)式編程已經(jīng)不再是學(xué)術(shù)界的最?lèi)?ài),開(kāi)始大踏步地在業(yè)界投入實(shí)用。也許繼面向?qū)ο缶幊讨螅瘮?shù)式編程會(huì)成為下一個(gè)編程的主流范式。 使用React也滿(mǎn)一年了,從剛剛會(huì)使用到逐漸探究其底層實(shí)現(xiàn),以便學(xué)習(xí)幾招奇技淫巧從而在自己的代碼中使用,寫(xiě)出高效的代碼。下面整理一些知識(shí)點(diǎn),算是React看書(shū)...
摘要:特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒(méi)想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請(qǐng)斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...
摘要:系列種優(yōu)化頁(yè)面加載速度的方法隨筆分類(lèi)中個(gè)最重要的技術(shù)點(diǎn)常用整理網(wǎng)頁(yè)性能管理詳解離線(xiàn)緩存簡(jiǎn)介系列編寫(xiě)高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪(fǎng)問(wèn)性能優(yōu)化方案實(shí)現(xiàn)的大排序算法一怪對(duì)象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁(yè)面加載速度的方法 隨筆分類(lèi) - HTML5 HTML5中40個(gè)最重要的技術(shù)點(diǎn) 常用meta整理 網(wǎng)頁(yè)性能管理詳解 HTML5 ...
閱讀 2849·2021-11-19 09:40
閱讀 3709·2021-11-15 18:10
閱讀 3290·2021-11-11 16:55
閱讀 1246·2021-09-28 09:36
閱讀 1663·2021-09-22 15:52
閱讀 3376·2019-08-30 14:06
閱讀 1171·2019-08-29 13:29
閱讀 2318·2019-08-26 17:04