一文搞懂ReactNative生命週期的進化

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"衆所周知每個應用的開發框架都有其對應的生命週期函數,ReactNative是基於React開發的,所以其生命週期先關函數也和React一樣密不可分,爲什麼文章標題叫“生命週期的進化”呢? 這是有原因的,因爲React在React 15和React 16兩個版本對生命週期函數做了優化調整,到底進行了那些調整和改進呢? 讓我們隨着本文一探究竟。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"React 15生命週期函數"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面這張圖是一個典型的React 15的生命週期函數流程圖,也是我們大多數開發者所瞭解到的。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2b/2b1723ffdc799ee307781227cb0d8a3a.png","alt":"在這裏插入圖片描述","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"React 15相關的生命週期函數如下:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"constructor()\ncomponentWillReceiveProps()\nshouldComponentUpdate()\ncomponentWillMount()\ncomponentWillUpdate()\ncomponentDidUpdate()\ncomponentDidMount()\nrender()\ncomponentWillUnmount()"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Mounting階段:組件初始化渲染"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"初始化渲染階段主要涉及如下幾個生命週期函數:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"constructor()\ncomponentWillMount()\nrender()\ncomponentDidMount()"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"constructor()這個函數只在組件初始化的調用一次,通常開發中主要用於對state的數據初始化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"componentWillMount(),componentDidMount()函數在組件初始化階段也只調用一次,componentWillMount()在render()函數執行之前被調用,有些同學開發中會在此函數中做網絡請求,想想會有哪些風險呀?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"之後觸發render()方法進行渲染頁面,但此時不會去操作真實DOM,只是把要渲染的頁面返回處理,真正處理是通過ReactDOM.render方法在頁面掛在階段完成的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"componentDidMount()函數會在真實DOM掛載完成時候調用,通常開發中我們在此函數進行網絡請求,消息監聽等來操作真實的DOM。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Updating階段:組件更新"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"組件的更新分爲兩種:一種是父組件的更新觸發的更新,另一種是組件自身調用this.setState()觸發的更新。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"組件更新過程中涉及的如下生命週期函數:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"componentWillReceiveProps()\nshouldComponentUpdate()\ncomponentWillUpdate()\nrender()\ncomponentDidUpdate()"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先看一下componentWillReceiveProps(nextProps),這個函數參數中nextProps表示接受到的新props,可以用來和this.props進行對比,看是否改變。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"請注意,如果父組件導致組件重新渲染,即使 props 沒有更改,也會調用此方法(componentWillReceiveProps)。如果只想處理更改,請確保進行當前值與變更值的比較。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">------ React官方文檔"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關鍵點:也就是componentWillReceiveProps()函數不是props改變觸發的,而是由於父組件更新觸發的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"shouldComponentUpdate(nextProps, nextState)函數在組件自身的state發生改變時觸發,該函數的返回值決定了組件是否重新render,默認返回“true”,表示只要是state發生更新,就會重新render;實際開發工作中,我們一般會對比函數中的參數來進行業務邏輯判斷是否需要重新render。這也是一個React提供給我們的一個性能優化的方向之一。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"componentWillUpdate()與componentDidUpdate()是在render前後進行觸發的,對應於componentWillMount()和componentDidMount()。componentDidUpdate在組件更新完成之後觸發,可以操作真實DOM。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Unmounting階段:組件卸載"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個階段就比較簡單了,只有一個生命週期函數:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"componentWillUnmount()"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在組件卸載和銷燬之前會觸發componentWillUnmount()函數,實際開發中我們通常進行如下操作,比如清除定時器timer, 取消網絡請求或者清除在 componentDidMount() 中創建的訂閱等。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"進化:React 16生命週期函數"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面節選我們回顧了React 15的生命週期函數,那麼React 16有那些更新和改進呢? 其中16.3版本和16.4版本的生命週期稍有不同,首先我們一起來16.3版本的流程圖"},{"type":"link","attrs":{"href":"https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/","title":""},"content":[{"type":"text","text":"React 16.3 Lifecycle"}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/db/dbfb1f4c556a5cdad6807e80c2133095.png","alt":"在這裏插入圖片描述","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"設計到的生命週期函數如下:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"constructor()\ngetDerivedStateFromProps()\ngetSnapshotBeforeUpdate()\nshouldComponentUpdate()\ncomponentDidUpdate()\ncomponentDidMount()\nrender()\ncomponentWillUnmount()"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Mounting階段:組件初始化階段(掛載)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在React 16的mounting階段涉及到的生命週期函數如下:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"constructor()\ngetDerivedStateFromProps()\ncomponentDidMount()\nrender()"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"React 16和React 15相比在初始化階段,多了一個getDerivedStateFromProps()函數,少了一個componentWillMount()函數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那getDerivedStateFromProps()是用來替換componentWillMount()函數的嗎?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先我們看一下這個函數"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"static getDerivedStateFromProps(props, state)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是一個靜態的函數,並且需要返回一個對象用來更新state,如果返回null,則不會更新state。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於此函數需要了解三個關鍵重要的點。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一個這是一個靜態函數,所以在這個函數中不能訪問class的實例,也就是說不能在函數中使用this通過this.props的獲取數據。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二個此函數接受兩個參數props和state,props是指父組件傳遞的props,state指的是自身的state。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三個此函數需要一個對象格式的返回值,因爲需要根據這個返回的數據來更新state,如果不需要更新state,就返回一個null, 如果什麼都不返回,就會收到警告,或者乾脆不重新這個函數。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Updating階段:組件更新階段"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在更新階段涉及到的函數有一下幾個:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"getDerivedStateFromProps()\nshouldComponentUpdate()\nrender()\ngetSnapshotBeforeUpdate()\ncomponentDidUpdate()"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在更新階段,16.3和16.4稍微有些不同,來看一下流程圖"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6d/6d4fafa65b21dfe5e9087d7901eb1706.png","alt":"在這裏插入圖片描述","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對比16.3和16.4的流程圖我們發現,變化的部分是getDerivedStateFromProps()的觸發時機,主要的變化體現在"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如下兩個方面:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.React 16.4版本中getDerivedStateFromProps()在父組件更新接受props,組件自身調用setState()函數以及forceUpdate()函數執行時都會被觸發"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.React 16.3在更新階段只有父組件更新纔會觸發。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對比React 15,我們發現React 16版本少了componentWillReceiveProps()以及componentWillUpdate(),增加了getDerivedStateFromProps()和getSnapshotBeforeUpdate()。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們知道React 15中父組件發生更新後會觸發componentWillReceiveProps()函數,而自身setState的時候不會觸發,而在React 16中組件任何更新都會觸發getDerivedStateFromProps()函數,這種改進是爲什麼呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於getDerivedStateFromProps,React給出的解釋是:"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與 componentDidUpdate 一起,這個新的生命週期涵蓋過時componentWillReceiveProps 的所有用例。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看出這個新函數不僅僅是componentWillReceiveProps的簡單替代,其方法被定義成了static,即在方法內部能不能訪問this,那麼就能夠避免錯誤的操作,比如使用this.fetch進行網絡請求,使用this.setSate進行render造成死循環。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此getDerivedStateFromProps 的存在只有一個目的:讓組件在 props 變化時更新 state。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於此函數的使用,官方也給出了建議:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意: 舊的 componentWillReceiveProps 和新的 getDerivedStateFromProps"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"方法都會給組件增加明顯的複雜性。這通常會導致 bug。考慮 "},{"type":"link","attrs":{"href":"https://zh-hans.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html","title":""},"content":[{"type":"text","text":"派生 state 的簡單替代方法"}]},{"type":"text","text":" 使組件可預測且可維護。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下來看getSnapshotBeforeUpdate,React官方的解釋:"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與 componentDidUpdate 一起,這個新的生命週期涵蓋過時的 componentWillUpdate 的所有用例。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"getSnapshotBeforeUpdate(prevProps, prevState)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"getSnapshotBeforeUpdate() 在最近一次渲染輸出(提交到 DOM 節點)之前調用。它使得組件能在發生更改之前從 DOM 中捕獲一些信息(例如,滾動位置)。此生命週期的任何返回值將作爲參數傳遞給 componentDidUpdate()。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這個函數裏面可以拿到更新前的真實DOM和更新後的最新props和state."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後看一下componentDidUpdate函數"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"componentDidUpdate(prevProps, prevState, snapshot)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果組件實現了 getSnapshotBeforeUpdate() 生命週期(不常用),則它的返回值將作爲 componentDidUpdate() 的第三個參數 “snapshot” 參數傳遞。否則此參數將爲 undefined。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Unmounting階段:組件卸載"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在卸載階段React 16和React 15一樣,沒有發生變化,都只涉及一個生命週期函數:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"componentWillUnmount()"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"componentWillUnmount() 中不應調用 setState(),因爲該組件將永遠不會重新渲染。組件實例卸載後,將永遠不會再掛載它。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"生命週期進化的原因"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"React 的生命週期發生變化和改進的原因都是因爲React 項目使用成爲“Fiber”的核心架構重寫了React。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Fiber使原來React 15的同步渲染變成了異步渲染,避免阻塞React 主線程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在React 16之前,我們使用setState更新組件,React都會生成一個新的虛擬DOM,通過與上一次的DOM進行diff對比後,再定向更新真實的DOM。這是一個同步渲染的遞歸過程,就如同走樓梯一樣。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f3/f317e434ddc866e8b8555f2b04823cf4.png","alt":"在這裏插入圖片描述","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果頁面的佈局複雜嵌套很深,那麼遞歸調用的時間就會很長,那麼的主線程就會被js一直佔用着,任何交互,佈局,渲染都會停止,那給用戶呈現的畫面就是很卡頓。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而使用Fiber重構之後就解決了這個問題,Fiber將漫長的更新任務進行切片成小任務。執行完一個小任務,就將主線程交換回去,看看是否有優先級更高的任務需要處理,這樣就避免了同步更新造成UI阻塞的問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用Fiber進行切片後,異步的渲染任務就變成了可打斷的,執行過程就變成了如下的模式:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7dfff1875576aeb840f241e9549d886.png","alt":"在這裏插入圖片描述","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"使用Fiber背後的故事"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在React 16以後,如下的幾個生命週期函數都被標記爲不安全的,"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"componentWillMount\ncomponentWillReceiveProps\ncomponentWillUpdate"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在這幾個生命週期函數前都增加了“UNSAFE_”前綴,變成如下模樣:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"UNSAFE_componentWillMount、\nUNSAFE_componentWillReceiveProps \nUNSAFE_componentWillUpdate"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲Fiber重構後,渲染變成了異步的,通過查看新的生命週期圖譜,這幾個方法都處於原來的render階段,也就是會出現重複調用的問題,比如說不合理的使用setState造成重複渲染死循環等。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總的來說,React生命週期的進化都是爲Fiber架構服務的,Fiber帶了異步渲染的機制,使生命週期變的更加純粹和可控,同時也減少了我們書寫代碼不規範造成的不必要的bug。"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章