摘要:更新階段卸載階段兄弟節(jié)點之間的通信主要是經(jīng)過父組件和也是通過改變父組件傳遞下來的實現(xiàn)的,滿足的設(shè)計遵循單向數(shù)據(jù)流模型,因此任何兩個組件之間的通信,本質(zhì)上都可以歸結(jié)為父子組件更新的情況。
你真的了解 React 生命周期嗎?
React 生命周期很多人都了解,但通常我們所了解的都是 單個組件 的生命周期,但針對 Hooks 組件、多個關(guān)聯(lián)組件(父子組件和兄弟組件) 的生命周期又是怎么樣的喃?你有思考和了解過嗎,接下來我們將完整的了解 React 生命周期。
關(guān)于 組件 ,我們這里指的是 React.Component 以及 React.PureComponent ,但是否包括 Hooks 組件喃?
一、Hooks 組件函數(shù)組件 的本質(zhì)是函數(shù),沒有 state 的概念的,因此不存在生命周期一說,僅僅是一個 render 函數(shù)而已。
但是引入 Hooks 之后就變得不同了,它能讓組件在不使用 class 的情況下?lián)碛?state,所以就有了生命周期的概念,所謂的生命周期其實就是 useState、 useEffect() 和 useLayoutEffect() 。
即:Hooks 組件(使用了Hooks的函數(shù)組件)有生命周期,而函數(shù)組件(未使用Hooks的函數(shù)組件)是沒有生命周期的。
下面,是具體的 class 與 Hooks 的生命周期對應(yīng)關(guān)系:
constructor:函數(shù)組件不需要構(gòu)造函數(shù),我們可以通過調(diào)用 useState 來初始化 state。如果計算的代價比較昂貴,也可以傳一個函數(shù)給 useState。
const [num, UpdateNum] = useState(0)
getDerivedStateFromProps:一般情況下,我們不需要使用它,我們可以在渲染過程中更新 state,以達到實現(xiàn) getDerivedStateFromProps 的目的。
function ScrollView({row}) { let [isScrollingDown, setIsScrollingDown] = useState(false); let [prevRow, setPrevRow] = useState(null); if (row !== prevRow) { // Row 自上次渲染以來發(fā)生過改變。更新 isScrollingDown。 setIsScrollingDown(prevRow !== null && row > prevRow); setPrevRow(row); } return `Scrolling down: ${isScrollingDown}`; }
React 會立即退出第一次渲染并用更新后的 state 重新運行組件以避免耗費太多性能。
shouldComponentUpdate:可以用?React.memo?包裹一個組件來對它的 props 進行淺比較
const Button = React.memo((props) => { // 具體的組件 });
注意:React.memo?等效于?PureComponent,它只淺比較 props。這里也可以使用 useMemo 優(yōu)化每一個節(jié)點。
render:這是函數(shù)組件體本身。
componentDidMount, componentDidUpdate:?useLayoutEffect?與它們兩的調(diào)用階段是一樣的。但是,我們推薦你一開始先用?useEffect,只有當(dāng)它出問題的時候再嘗試使用?useLayoutEffect。useEffect 可以表達所有這些的組合。
// componentDidMount useEffect(()=>{ // 需要在 componentDidMount 執(zhí)行的內(nèi)容 }, []) useEffect(() => { // 在 componentDidMount,以及 count 更改時 componentDidUpdate 執(zhí)行的內(nèi)容 document.title = `You clicked ${count} times`; return () => { // 需要在 count 更改時 componentDidUpdate(先于 document.title = ... 執(zhí)行,遵守先清理后更新) // 以及 componentWillUnmount 執(zhí)行的內(nèi)容 } // 當(dāng)函數(shù)中 Cleanup 函數(shù)會按照在代碼中定義的順序先后執(zhí)行,與函數(shù)本身的特性無關(guān) }, [count]); // 僅在 count 更改時更新
請記得 React 會等待瀏覽器完成畫面渲染之后才會延遲調(diào)用?useEffect,因此會使得額外操作很方便
componentWillUnmount:相當(dāng)于 useEffect 里面返回的 cleanup 函數(shù)
// componentDidMount/componentWillUnmount useEffect(()=>{ // 需要在 componentDidMount 執(zhí)行的內(nèi)容 return function cleanup() { // 需要在 componentWillUnmount 執(zhí)行的內(nèi)容 } }, [])
componentDidCatch and getDerivedStateFromError:目前還沒有這些方法的 Hook 等價寫法,但很快會加上。
為方便記憶,大致匯總成表格如下。
class 組件 | Hooks 組件 |
---|---|
constructor | useState |
getDerivedStateFromProps | useState 里面 update 函數(shù) |
shouldComponentUpdate | useMemo |
render | 函數(shù)本身 |
componentDidMount | useEffect |
componentDidUpdate | useEffect |
componentWillUnmount | useEffect 里面返回的函數(shù) |
componentDidCatch | 無 |
getDerivedStateFromError | 無 |
我們可以將生命周期分為三個階段:
掛載階段
組件更新階段
卸載階段
分開來講:
掛載階段
constructor:避免將 props 的值復(fù)制給 state
componentWillMount
render:react 最重要的步驟,創(chuàng)建虛擬 dom,進行 diff 算法,更新 dom 樹都在此進行
componentDidMount
組件更新階段
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
卸載階段
componentWillUnMount
這種生命周期會存在一個問題,那就是當(dāng)更新復(fù)雜組件的最上層組件時,調(diào)用棧會很長,如果在進行復(fù)雜的操作時,就可能長時間阻塞主線程,帶來不好的用戶體驗,Fiber 就是為了解決該問題而生。
Fiber 本質(zhì)上是一個虛擬的堆棧幀,新的調(diào)度器會按照優(yōu)先級自由調(diào)度這些幀,從而將之前的同步渲染改成了異步渲染,在不影響體驗的情況下去分段計算更新。
對于異步渲染,分為兩階段:
reconciliation:
componentWillMount
componentWillReceiveProps
shouldConmponentUpdate
componentWillUpdate
commit
componentDidMount
componentDidUpdate
其中,reconciliation 階段是可以被打斷的,所以 reconcilation 階段執(zhí)行的函數(shù)就會出現(xiàn)多次調(diào)用的情況,顯然,這是不合理的。
所以 V16.3 引入了新的 API 來解決這個問題:
static getDerivedStateFromProps:?該函數(shù)在掛載階段和組件更新階段都會執(zhí)行,即每次獲取新的props 或 state 之后都會被執(zhí)行,在掛載階段用來代替componentWillMount;在組件更新階段配合 componentDidUpdate,可以覆蓋 componentWillReceiveProps 的所有用法。
同時它是一個靜態(tài)函數(shù),所以函數(shù)體內(nèi)不能訪問 this,會根據(jù) nextProps 和 prevState 計算出預(yù)期的狀態(tài)改變,返回結(jié)果會被送給 setState,返回 null 則說明不需要更新 state,并且這個返回是必須的。
getSnapshotBeforeUpdate: 該函數(shù)會在?render?之后, DOM 更新前被調(diào)用,用于讀取最新的 DOM 數(shù)據(jù)。
返回一個值,作為 componentDidUpdate 的第三個參數(shù);配合 componentDidUpdate, 可以覆蓋componentWillUpdate 的所有用法。
注意:V16.3 中只用在組件掛載或組件 props 更新過程才會調(diào)用,即如果是因為自身 setState 引發(fā)或者forceUpdate 引發(fā),而不是由父組件引發(fā)的話,那么static getDerivedStateFromProps也不會被調(diào)用,在 V16.4 中更正為都調(diào)用。
即更新后的生命周期為:
掛載階段
constructor
static getDerivedStateFromProps
render
componentDidMount
更新階段
static getDerivedStateFromProps
shouldComponentUpdate
render
getSnapshotBeforeUpdate
componentDidUpdate
卸載階段
componentWillUnmount
2. 生命周期,誤區(qū)誤解一:getDerivedStateFromProps?和?componentWillReceiveProps?只會在 props 改變 時才會調(diào)用
實際上,只要父級重新渲染,getDerivedStateFromProps?和?componentWillReceiveProps?都會重新調(diào)用,不管 props 有沒有變化。所以,在這兩個方法內(nèi)直接將 props 賦值到 state 是不安全的。
// 子組件 class PhoneInput extends Component { state = { phone: this.props.phone }; handleChange = e => { this.setState({ phone: e.target.value }); }; render() { const { phone } = this.state; return ; } componentWillReceiveProps(nextProps) { // 不要這樣做。 // 這會覆蓋掉之前所有的組件內(nèi) state 更新! this.setState({ phone: nextProps.phone }); } } // 父組件 class App extends Component { constructor() { super(); this.state = { count: 0 }; } componentDidMount() { // 使用了 setInterval, // 每秒鐘都會更新一下 state.count // 這將導(dǎo)致 App 每秒鐘重新渲染一次 this.interval = setInterval( () => this.setState(prevState => ({ count: prevState.count + 1 })), 1000 ); } componentWillUnmount() { clearInterval(this.interval); } render() { return ( <>Start editing to see some magic happen :)
This component will re-render every second. Each time it renders, the text you type will be reset. This illustrates a derived state anti-pattern.
> ); } }
實例可點擊這里查看
當(dāng)然,我們可以在 父組件App 中 shouldComponentUpdate 比較?props 的 email 是不是修改再決定要不要重新渲染,但是如果子組件接受多個 props(較為復(fù)雜),就很難處理,而且 shouldComponentUpdate 主要是用來性能提升的,不推薦開發(fā)者操作 shouldComponetUpdate(可以使用 React.PureComponet)。
我們也可以使用 在 props 變化后修改 state。
class PhoneInput extends Component { state = { phone: this.props.phone }; componentWillReceiveProps(nextProps) { // 只要 props.phone 改變,就改變 state if (nextProps.phone !== this.props.phone) { this.setState({ phone: nextProps.phone }); } } // ... }
但這種也會導(dǎo)致一個問題,當(dāng) props 較為復(fù)雜時,props 與 state 的關(guān)系不好控制,可能導(dǎo)致問題
解決方案一:完全可控的組件
function PhoneInput(props) { return ; }
完全由 props 控制,不派生 state
解決方案二:有 key 的非可控組件
class PhoneInput extends Component { state = { phone: this.props.defaultPhone }; handleChange = event => { this.setState({ phone: event.target.value }); }; render() { return ; } }
當(dāng)?key?變化時, React 會創(chuàng)建一個新的而不是更新一個既有的組件
誤解二:將 props 的值直接復(fù)制給 state
應(yīng)避免將 props 的值復(fù)制給 state
constructor(props) { super(props); // 千萬不要這樣做 // 直接用 props,保證單一數(shù)據(jù)源 this.state = { phone: props.phone }; }三、多個組件的執(zhí)行順序 1. 父子組件
掛載階段
分 兩個 階段:
第 一 階段,由父組件開始執(zhí)行到自身的 render,解析其下有哪些子組件需要渲染,并對其中 同步的子組件 進行創(chuàng)建,按 遞歸順序 挨個執(zhí)行各個子組件至 render,生成到父子組件對應(yīng)的 Virtual DOM 樹,并 commit 到 DOM。
第 二 階段,此時 DOM 節(jié)點已經(jīng)生成完畢,組件掛載完成,開始后續(xù)流程。先依次觸發(fā)同步子組件各自的 componentDidMount,最后觸發(fā)父組件的。
注意:如果父組件中包含異步子組件,則會在父組件掛載完成后被創(chuàng)建。
所以執(zhí)行順序是:
父組件 getDerivedStateFromProps —> 同步子組件 getDerivedStateFromProps —> 同步子組件 componentDidMount —> 父組件 componentDidMount —> 異步子組件 getDerivedStateFromProps —> 異步子組件 componentDidMount
更新階段
React 的設(shè)計遵循單向數(shù)據(jù)流模型 ,也就是說,數(shù)據(jù)均是由父組件流向子組件。
第 一 階段,由父組件開始,執(zhí)行
static getDerivedStateFromProps
shouldComponentUpdate
更新到自身的 `render`,解析其下有哪些子組件需要渲染,并對 **子組件** 進行創(chuàng)建,按 **遞歸順序** 挨個執(zhí)行各個子組件至 `render`,生成到父子組件對應(yīng)的 Virtual DOM 樹,并與已有的 Virtual DOM 樹 比較,計算出 **Virtual DOM 真正變化的部分** ,并只針對該部分進行的原生DOM操作。
第 二 階段,此時 DOM 節(jié)點已經(jīng)生成完畢,組件掛載完成,開始后續(xù)流程。先依次觸發(fā)同步子組件以下函數(shù),最后觸發(fā)父組件的。
getSnapshotBeforeUpdate()
componentDidUpdate()
React 會按照上面的順序依次執(zhí)行這些函數(shù),每個函數(shù)都是各個子組件的先執(zhí)行,然后才是父組件的執(zhí)行。 所以執(zhí)行順序是: 父組件 getDerivedStateFromProps —> 父組件 shouldComponentUpdate —> 子組件 getDerivedStateFromProps —> 子組件 shouldComponentUpdate —> 子組件 getSnapshotBeforeUpdate —> 父組件 getSnapshotBeforeUpdate —> 子組件 componentDidUpdate —> 父組件 componentDidUpdate
卸載階段
componentWillUnmount(),順序為 父組件的先執(zhí)行,子組件按照在 JSX 中定義的順序依次執(zhí)行各自的方法。
注意 :如果卸載舊組件的同時伴隨有新組件的創(chuàng)建,新組件會先被創(chuàng)建并執(zhí)行完 render,然后卸載不需要的舊組件,最后新組件執(zhí)行掛載完成的回調(diào)。
2. 兄弟組件掛載階段
若是同步路由,它們的創(chuàng)建順序和其在共同父組件中定義的先后順序是 一致 的。
若是異步路由,它們的創(chuàng)建順序和 js 加載完成的順序一致。
更新階段、卸載階段
兄弟節(jié)點之間的通信主要是經(jīng)過父組件(Redux 和 Context 也是通過改變父組件傳遞下來的?props?實現(xiàn)的),滿足React 的設(shè)計遵循單向數(shù)據(jù)流模型, 因此任何兩個組件之間的通信,本質(zhì)上都可以歸結(jié)為父子組件更新的情況 。
所以,兄弟組件更新、卸載階段,請參考 父子組件。
走在最后:走心推薦一個在線編輯工具:StackBlitz,可以在線編輯 Angular、React、TypeScript、RxJS、Ionic、Svelte項目
預(yù)告:后續(xù)將加入高階組件的生命周期,敬請期待小瓶子的下次更新。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/106133.html
摘要:第一次了解這項特性的時候,真的有一種豁然開朗,發(fā)現(xiàn)新大陸的感覺。為了解決這一痛點,才會有剪頭函數(shù)的綁定特性。它同時具備和三個生命周期函數(shù)的執(zhí)行時機。 歡迎關(guān)注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 React Hooks 是從 v16.8 引入的又一開創(chuàng)性的新特性。第一次了解這項特性...
摘要:比如在條件判斷中使用,在循環(huán),嵌套函數(shù)中使用,都會造成執(zhí)行順序不一致的問題。而比如定時器,事件監(jiān)聽。第一個參數(shù)的返回值,會在組件卸載時執(zhí)行,相當(dāng)于,可以清理定時器,移除事件監(jiān)聽,取消一些訂閱。 什么是 Hooks? 不通過編寫類組件的情況下,可以在組件內(nèi)部使用狀態(tài)(state) 和其他 React 特性(生命周期,context)的技術(shù) Hooks 為什么會出現(xiàn) 在之前的 React ...
摘要:已經(jīng)被廢除,具體缺陷可以參考二為了解決的缺陷,第二種解決方案是高階組件簡稱。我們定義了父組件,存在自身的,并且將自身的通過的方式傳遞給了子組件。返回一個標(biāo)識該的變量,以及更新該的方法。 ??為了實現(xiàn)分離業(yè)務(wù)邏輯代碼,實現(xiàn)組件內(nèi)部相關(guān)業(yè)務(wù)邏輯的復(fù)用,在React的迭代中針對類組件中的代碼復(fù)用依次發(fā)布了Mixin、HOC、Render props等幾個方案。此外,針對函數(shù)組件,在Reac...
摘要:已經(jīng)被廢除,具體缺陷可以參考二為了解決的缺陷,第二種解決方案是高階組件簡稱。我們定義了父組件,存在自身的,并且將自身的通過的方式傳遞給了子組件。返回一個標(biāo)識該的變量,以及更新該的方法。 ??為了實現(xiàn)分離業(yè)務(wù)邏輯代碼,實現(xiàn)組件內(nèi)部相關(guān)業(yè)務(wù)邏輯的復(fù)用,在React的迭代中針對類組件中的代碼復(fù)用依次發(fā)布了Mixin、HOC、Render props等幾個方案。此外,針對函數(shù)組件,在Reac...
摘要:已經(jīng)被廢除,具體缺陷可以參考二為了解決的缺陷,第二種解決方案是高階組件簡稱。我們定義了父組件,存在自身的,并且將自身的通過的方式傳遞給了子組件。返回一個標(biāo)識該的變量,以及更新該的方法。 ??為了實現(xiàn)分離業(yè)務(wù)邏輯代碼,實現(xiàn)組件內(nèi)部相關(guān)業(yè)務(wù)邏輯的復(fù)用,在React的迭代中針對類組件中的代碼復(fù)用依次發(fā)布了Mixin、HOC、Render props等幾個方案。此外,針對函數(shù)組件,在Reac...
閱讀 2316·2021-11-16 11:51
閱讀 3529·2021-09-26 10:14
閱讀 1857·2021-09-22 15:58
閱讀 1110·2019-08-30 15:52
閱讀 2026·2019-08-30 15:43
閱讀 2624·2019-08-30 13:46
閱讀 923·2019-08-30 13:10
閱讀 1034·2019-08-29 18:32