摘要:另外本文中會(huì)介紹一個(gè)通過(guò)類繼承方式定義的組件的生命周期,以及在各個(gè)生命周期函數(shù)中能做什么,不能或盡量不要做什么。各個(gè)生命周期函數(shù)介紹及使用經(jīng)驗(yàn)。獲取組件的初始內(nèi)部狀態(tài)在中。該聲明周期函數(shù)可能在兩種情況下被調(diào)用組件接收到了新的屬性。
文章標(biāo)題總算是可以正常一點(diǎn)了……
通過(guò)之前的文章我們已經(jīng)知道:在 React 體系中所謂的 "在 JavaScript 中編寫(xiě) HTML 代碼" 指的是 React 擴(kuò)展了 JavaScript 的語(yǔ)法,也就是 JSX。JSX 語(yǔ)法中可以以類似 HTML 語(yǔ)法的方式使用 React 組件,從而編寫(xiě) React 組件就有一種創(chuàng)造一個(gè)新的 HTML 標(biāo)簽的體驗(yàn)。
上一篇文章《玩轉(zhuǎn) React(四)- 創(chuàng)造一個(gè)新的 HTML 標(biāo)簽》介紹了如何來(lái)創(chuàng)建一個(gè) React 組件,以及組件的屬性。了解到組件的視圖是屬性的映射,通過(guò)改變組件屬性可以觸發(fā)組件重新渲染,從而改變組件的視圖。其實(shí)組件的視圖并不僅僅是由屬性映射來(lái)的,本篇將介紹另一種可以觸發(fā)組件重新渲染的方式,即組件的內(nèi)部狀態(tài)(state),嚴(yán)格來(lái)說(shuō)組件的視圖是由屬性和內(nèi)部狀態(tài)映射而來(lái)的,即:view = f(props, state),跟屬性類似,狀態(tài)的改變也會(huì)觸發(fā)組件重新渲染,只不過(guò)狀態(tài)是組件內(nèi)部基于自身邏輯或者用戶事件自己維護(hù)的,而不是由外部輸入的。
另外本文中會(huì)介紹一個(gè)通過(guò)類繼承方式定義的組件的生命周期,以及在各個(gè)生命周期函數(shù)中能做什么,不能或盡量不要做什么。
內(nèi)容摘要ReactDOM.render 在一個(gè)單頁(yè)面 web 應(yīng)用中通常只調(diào)用一次。
組件可以通過(guò) setState 改變內(nèi)部狀態(tài) state 來(lái)更新視圖。
setState 多數(shù)情況下是異步的。
不要直接使用當(dāng)前 state 的值生成下一個(gè) state。
不要直接通過(guò) this.state 修改 state。
組件生命周期流程圖。
各個(gè)生命周期函數(shù)介紹及使用經(jīng)驗(yàn)。
以上是本文的內(nèi)容摘要,如果你已經(jīng)知道我要說(shuō)的是什么,那么就沒(méi)有必要繼續(xù)看下去了,節(jié)約時(shí)間。
組件的內(nèi)部狀態(tài)此前,我們已經(jīng)了解到可以通過(guò) ReactDOM.render(
下面是官方文檔中一個(gè)展示時(shí)鐘的例子,我簡(jiǎn)單改造了下:
https://codepen.io/Sarike/pen...
例子中定義了一個(gè) Clock 組件,組件接收一個(gè) time 屬性,在組件外部通過(guò) setInterval 周期性地調(diào)用 ReactDOM.render 不斷更新 Clock 的屬性并重新渲染。
然而在很多實(shí)際場(chǎng)景中,對(duì)于一個(gè)時(shí)鐘組件,我們希望它有更好的封裝性和復(fù)用性,也就是說(shuō)我們希望只調(diào)用一次 ReactDOM.render(
要達(dá)到這個(gè)目的,就需要組件的內(nèi)部狀態(tài)來(lái)支持。組件有一個(gè)特殊的屬性 state 用來(lái)保存組件的內(nèi)部狀態(tài)。用戶可以通過(guò) this.setState(statePatch) 來(lái)更新組件的狀態(tài),組件的狀態(tài)更新后會(huì)重新執(zhí)行 render 方法來(lái)更新視圖,上面的例子使用內(nèi)部狀態(tài)改造后:
https://codepen.io/Sarike/pen...
這樣 Clock 作為一個(gè)完整的時(shí)鐘組件就可以自己來(lái)更新自己了,上篇文中也有提到過(guò),如果想要使用組件的內(nèi)部狀態(tài),那組件必須以類繼承的方式來(lái)定義,而不能使用函數(shù)式組件。所以說(shuō),函數(shù)式組件經(jīng)常也被稱作是無(wú)狀態(tài)組件(stateless)。
上面例子中有用到 componentDidMount 和 componentWillUnmount 兩個(gè)函數(shù),它們是組件的生命周期函數(shù),本文的后半部分將會(huì)介紹,這倆函數(shù)分別在組件掛載到頁(yè)面上和組件將要從頁(yè)面上移除時(shí)調(diào)用。
改造后的例子,我們只需要調(diào)用一次 ReactDOM.render 即可,在實(shí)際的項(xiàng)目中,一個(gè)完整的單頁(yè)面 web 應(yīng)用,也只需要調(diào)用一次 ReactDOM.render 方法把根組件掛載到頁(yè)面中即可,剩下的工作就都放心地交給 React 就行了。
初始化組件內(nèi)部狀態(tài)在創(chuàng)建一個(gè)擁有內(nèi)部狀態(tài)的組件時(shí),我們需要對(duì)內(nèi)部狀態(tài)進(jìn)行初始化,即設(shè)置組件最初的狀態(tài)是什么。做法很簡(jiǎn)單,就是在構(gòu)造函數(shù) constructor 中設(shè)置 state 屬性就可以了。如下所示:
class MyComponent extends React.Component { constructor(props) { super(props); // 這行代碼不能少哦 this.state = { name: "Lucy" } } }setState 大多數(shù)情況下是異步的
setState 多數(shù)情況下是異步的,異步意味著通過(guò) setState 更新組件狀態(tài)后,不能立刻通過(guò) this.state 來(lái)獲取到更新之后的值,另外當(dāng)連續(xù)多次調(diào)用 setState 來(lái)更新同一個(gè)字段時(shí),只有最后一次更新才會(huì)生效。如下示例:
https://codepen.io/Sarike/pen...
如果希望上面示例代碼正常工作,你需要通過(guò)回調(diào)函數(shù)的方式來(lái)生成下一個(gè) state,如下所示:
this.setState(preState => ({value: preState.value + 1})); this.setState(preState => ({value: preState.value + 2})); this.setState(preState => ({value: preState.value + 3}));
所以,直接基于當(dāng)前 state 的值,生成一下個(gè) state 是不靠譜的,但是很多不清楚這一點(diǎn)的同學(xué)基本上都是這么做的,因?yàn)閷?xiě)起來(lái)簡(jiǎn)單嘛,而且貌似也沒(méi)有什么問(wèn)題。這是因?yàn)楹芏嗲闆r下,業(yè)務(wù)邏輯沒(méi)有那么復(fù)雜,基本不會(huì)頻繁調(diào)用 setState 。但是這確實(shí)是一個(gè)隱患,如果在項(xiàng)目初期不注意規(guī)避,等項(xiàng)目復(fù)雜到一定程度以后,可能會(huì)出現(xiàn)難以排查的BUG。
那為什么說(shuō)多數(shù)情況下是異步的呢?難道有些情況下不是異步的嗎?是的,實(shí)際上只有在 React 能控制的事件處理過(guò)程中調(diào)用的 setState 才是異步的,如:生命周期函數(shù),React 內(nèi)置的如 button,input 等組件的事件處理函數(shù)。在多數(shù)的情況下我們只需要在這些地方控制我們的組件就夠了,所以說(shuō)大多數(shù)情況下 setState 是異步的。
在某些特殊的組件中,可能需要通過(guò) addEventListener 來(lái)設(shè)置某些 DOM 的事件處理函數(shù),在這種通過(guò)原生的 JS API 來(lái)設(shè)置的事件處理過(guò)程調(diào)用 setState 就是同步的,會(huì)立即更新 this.state。另外還有 setInterval、setTimeout 等原生 API 的回調(diào)函數(shù)也是如此。
參考:https://www.zhihu.com/questio...
不要直接通過(guò) this.state 來(lái)更新組件狀態(tài)這一點(diǎn)跟屬性類似,直接通過(guò) this.state 修改組件狀態(tài),組件狀態(tài)被修改了,但并不會(huì)觸發(fā)組件的重新渲染。這樣就會(huì)導(dǎo)致組件視圖與狀態(tài)不一致。
生命周期函數(shù)一個(gè)組件被我們創(chuàng)造到這個(gè)世界上之后,在使用它時(shí),它的每個(gè)實(shí)例都是有一定生命周期的,下面這張圖說(shuō)明了一個(gè)組件實(shí)例的生命周期:
圖片來(lái)源:https://tylermcginnis.com/an-...,這張圖略微有點(diǎn)老,不過(guò)結(jié)合下文來(lái)看也沒(méi)什么問(wèn)題。
下面我們來(lái)解釋一下上面這張圖。
組件初始化:constructor我們定義的每一個(gè)組件,都是一個(gè)類(class),這些類被實(shí)例化后才能作為 React DOM 中的一個(gè)節(jié)點(diǎn)渲染到頁(yè)面上。所以,當(dāng)我們通過(guò) ReactDOM.render 或者在某個(gè)組件中通過(guò) JSX 表達(dá)式將一個(gè)組件第一次渲染到頁(yè)面上時(shí),組件首先要做的就是對(duì)組件進(jìn)行實(shí)例化。
實(shí)例化主要做的事情:
創(chuàng)建一個(gè)組件的實(shí)例對(duì)象(也就是 Element,通常對(duì)應(yīng)一個(gè)JSX表達(dá)式,如:
獲取組件的默認(rèn)屬性。
獲取組件的初始內(nèi)部狀態(tài)(在 constructor 中 this.state = xxxx;)。
componentWillMount在組件被渲染到頁(yè)面上之前執(zhí)行,在組件的整個(gè)生命周期內(nèi)只執(zhí)行一次。在這里可以調(diào)用 setState 更新內(nèi)部狀態(tài),但是更推薦將這里的狀態(tài)更新操作放到 constructor 中。
該函數(shù)執(zhí)行完后會(huì)立馬執(zhí)行 render 方法并將組件渲染到頁(yè)面上。所以,在這里執(zhí)行 setState 不會(huì)觸發(fā)額外的渲染過(guò)程,因?yàn)檫@是沒(méi)有必要的。
componentDidMount組件被渲染到頁(yè)面上后立馬執(zhí)行,在組件的整個(gè)生命周期內(nèi)只執(zhí)行一次。這個(gè)時(shí)候是做如下操作的好時(shí)機(jī):
某些依賴組件 DOM 節(jié)點(diǎn)的操作。
發(fā)起網(wǎng)絡(luò)請(qǐng)求。
設(shè)置 setInterval、setTimeout 等計(jì)時(shí)器操作。
在這里可以調(diào)用 setState 更新組件內(nèi)部狀態(tài),且會(huì)觸發(fā)一個(gè)重新渲染的過(guò)程,即會(huì)重新執(zhí)行 render 方法并更新視圖。
componentWillReceivePropscomponentWillReceiveProps(nextProps)
該聲明周期函數(shù)可能在兩種情況下被調(diào)用:
組件接收到了新的屬性。新的屬性會(huì)通過(guò) nextProps 獲取到。
組件沒(méi)有收到新的屬性,但是由于父組件重新渲染導(dǎo)致當(dāng)前組件也被重新渲染。
你只要知道,當(dāng)該函數(shù)被調(diào)用時(shí),并不一定是因?yàn)閷傩园l(fā)生了變化。
在這里也可以調(diào)用 setState 更新組件的內(nèi)部狀態(tài),同樣也不會(huì)觸發(fā)額外的重新渲染操作,React 會(huì)聰明地用更新后的屬性和內(nèi)部狀態(tài)進(jìn)行一次重新渲染。
shouldComponentUpdateshouldComponentUpdate(nextProps, nextState)
這是一個(gè)詢問(wèn)式的生命周期函數(shù),所以該函數(shù)需要一個(gè)返回值 true/false,如果為 true,組件將觸發(fā)重新渲染過(guò)程,如果為 false 組件將不會(huì)觸發(fā)重新渲染。因此,合理地利用該函數(shù)可以一定程度節(jié)省開(kāi)銷,提高系統(tǒng)的性能。
此處不能調(diào)用 setState 更新組件的狀態(tài)。
由于組件屬性或者內(nèi)部狀態(tài)被改變時(shí)都觸發(fā)組件重新渲染,所以該函數(shù)接受兩個(gè)參數(shù):新的屬性(nextProps)、新的狀態(tài)(nextState)。
在處理該聲明周期函數(shù)時(shí),切記要兼顧屬性和狀態(tài),不能只顧其一,不然很容易踩坑。例如:某位同學(xué)只依據(jù)屬性來(lái)判斷是否觸發(fā)重新渲染,而忽略了內(nèi)部狀態(tài),這樣就導(dǎo)致你無(wú)論如何 setState,組件視圖都不能正常更新。
在上篇文章中我們提到類繼承方式定義組件時(shí)說(shuō)到,React 提供了兩個(gè)基類,一個(gè)是 Component,另一個(gè)是 PureComponent,兩者的差別就在于后者已經(jīng)幫我們簡(jiǎn)單實(shí)現(xiàn)了一下 shouldComponentUpdate 函數(shù),當(dāng)屬性和狀態(tài)都沒(méi)有發(fā)生變化時(shí)返回 false 以避免額外的開(kāi)銷。
但是比對(duì)過(guò)程出于性能考慮,只是進(jìn)行淺比對(duì),也就是只比對(duì)對(duì)象的第一級(jí)字段,而且是否發(fā)生變化是通過(guò) Object.is 方法類判斷的。所以會(huì)導(dǎo)致有時(shí)候發(fā)生變化了組件沒(méi)有更新,沒(méi)有變化卻觸發(fā)了重新渲染過(guò)程。這個(gè)在這里不再贅述,想深入探討可以掃描問(wèn)候的二維碼加我微信好友(我的微信:leobaba88)。
componentWillUpdate當(dāng)組件 shouldComponentUpdate 返回 true 或者調(diào)用 forceUpdate 時(shí)將觸發(fā)此函數(shù)。
該函數(shù)中不能調(diào)用 setState 更新組件狀態(tài),當(dāng)你想這么做的時(shí)候,你可以考慮將它移到 componentWillReceiveProps 函數(shù)里。
該函數(shù)在函數(shù)第一次渲染的時(shí)候不會(huì)執(zhí)行。
componentDidUpdatecomponentDidUpdate(prevProps, prevState)
在組件重新渲染過(guò)程中,重新執(zhí)行 render 方法并更新組件視圖后立即執(zhí)行該函數(shù)。類似組件第一次渲染過(guò)程中的 componentDidMount,該函數(shù)在第一次渲染時(shí)不會(huì)執(zhí)行。
在此處是做這些事情的好時(shí)機(jī):
執(zhí)行依賴新 DOM 節(jié)點(diǎn)的操作。
依據(jù)新的屬性發(fā)起新的網(wǎng)絡(luò)請(qǐng)求。(但是此處一定要格外謹(jǐn)慎,一定要在確認(rèn)屬性變化后再發(fā)起網(wǎng)絡(luò)請(qǐng)求,不然極有可能進(jìn)入死循環(huán):didUpdate -> ajax -> changeProps -> didUpdate -> ...)。
componentWillUnmount當(dāng)組件被從頁(yè)面中移除之前調(diào)用,此時(shí)是清理戰(zhàn)場(chǎng)的好時(shí)機(jī),如清理定時(shí)器、終止網(wǎng)絡(luò)請(qǐng)求等。
componentDidCatchcomponentDidCatch(error, info)
這是 React 16 新加入的一個(gè)生命周期函數(shù)。定義該生命周期函數(shù)的組件將會(huì)成為一個(gè)錯(cuò)誤邊界,錯(cuò)誤邊界這個(gè)詞非常形象,它可以有效地將錯(cuò)誤限制在一個(gè)有限的范圍內(nèi),而不會(huì)導(dǎo)致整個(gè)應(yīng)用崩潰,防止一顆耗子屎壞了一鍋湯。
錯(cuò)誤邊界組件,可以捕獲其整個(gè)子組件樹(shù)內(nèi)發(fā)生的任何異常,但是卻不能捕獲自身的異常。
下面是官方的一個(gè)示例,大家感受下:
https://codepen.io/gaearon/pe...
最后(微信群)這篇文章來(lái)的有點(diǎn)慢,非常抱歉。
另外為了方便大家閱讀,我將所有文章的鏈接更新到第一篇文章 《玩轉(zhuǎn)React(一)- 前言》 中。
文字的表現(xiàn)范圍畢竟有限,為了方便大家交流,我建了一個(gè)微信群,對(duì) React 感興趣的同學(xué)可以進(jìn)群一起交流、學(xué)習(xí),由于微信群邀請(qǐng)的時(shí)間限制,大家可以先掃描下面二維碼,加我好友,我拉大家進(jìn)群:
我的微信:leobaba88
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/51427.html
摘要:另外本文中會(huì)介紹一個(gè)通過(guò)類繼承方式定義的組件的生命周期,以及在各個(gè)生命周期函數(shù)中能做什么,不能或盡量不要做什么。各個(gè)生命周期函數(shù)介紹及使用經(jīng)驗(yàn)。獲取組件的初始內(nèi)部狀態(tài)在中。該聲明周期函數(shù)可能在兩種情況下被調(diào)用組件接收到了新的屬性。 文章標(biāo)題總算是可以正常一點(diǎn)了…… 通過(guò)之前的文章我們已經(jīng)知道:在 React 體系中所謂的 在 JavaScript 中編寫(xiě) HTML 代碼 指的是 Rea...
摘要:本人計(jì)劃編寫(xiě)一個(gè)針對(duì)中初級(jí)前端開(kāi)發(fā)者學(xué)習(xí)的系列教程玩轉(zhuǎn)。使用的原因是新的語(yǔ)言規(guī)范開(kāi)發(fā)效率更高代碼更優(yōu)雅,尤其是基于開(kāi)發(fā)的項(xiàng)目。其次也是目前特別流行的一個(gè)前端框架,截止目前,上有將近萬(wàn),國(guó)內(nèi)一二線互聯(lián)網(wǎng)公司都有深度依賴開(kāi)發(fā)的項(xiàng)目。 本人計(jì)劃編寫(xiě)一個(gè)針對(duì)中初級(jí)前端開(kāi)發(fā)者學(xué)習(xí) React 的系列教程 - 《玩轉(zhuǎn) React》。 文章更新頻率:每周 1 ~ 2 篇。 目錄 玩轉(zhuǎn) React(...
摘要:屬性是一個(gè)組件的外部輸入。只會(huì)在開(kāi)發(fā)模式下進(jìn)行屬性類型檢查,當(dāng)代碼進(jìn)行生產(chǎn)發(fā)布后,為了減少額外的性能開(kāi)銷,類型檢查將會(huì)被略過(guò)。某個(gè)類的實(shí)例枚舉,屬性值必須為其中的某一個(gè)值。屬性為一個(gè)數(shù)組,且數(shù)組中的元素必須符合指定類型。 在第二篇文章 《新型前端開(kāi)發(fā)方式》 中有說(shuō)到 React 有很爽的一點(diǎn)就是給我們一種創(chuàng)造 HTML 標(biāo)簽的能力,那么今天這篇文章就詳細(xì)講解下 React 是如何提供這...
摘要:屬性是一個(gè)組件的外部輸入。只會(huì)在開(kāi)發(fā)模式下進(jìn)行屬性類型檢查,當(dāng)代碼進(jìn)行生產(chǎn)發(fā)布后,為了減少額外的性能開(kāi)銷,類型檢查將會(huì)被略過(guò)。某個(gè)類的實(shí)例枚舉,屬性值必須為其中的某一個(gè)值。屬性為一個(gè)數(shù)組,且數(shù)組中的元素必須符合指定類型。 在第二篇文章 《新型前端開(kāi)發(fā)方式》 中有說(shuō)到 React 有很爽的一點(diǎn)就是給我們一種創(chuàng)造 HTML 標(biāo)簽的能力,那么今天這篇文章就詳細(xì)講解下 React 是如何提供這...
閱讀 3674·2021-11-23 09:51
閱讀 1036·2021-11-19 11:30
閱讀 3360·2019-08-29 14:16
閱讀 3370·2019-08-29 12:12
閱讀 2363·2019-08-26 13:40
閱讀 3471·2019-08-26 12:21
閱讀 3073·2019-08-26 11:55
閱讀 2221·2019-08-26 11:35