能否理解一個元件的生命週期是非常重要的,甚至可以說是最重要的內容,他是一切進階的基石,下面我們就從angular1的生命週期開始吧。
angular1生命週期
angular1的生命週期在1.5之前是很晦澀的,controller、compile、preLink、postLink,compile的return函式。從字面上讓人完全摸不著頭腦,到了1.5增加了angular.component,這一問題有了很大改觀,生命週期全部放到controller(controller不再作為一個生命週期鉤子存在),controller就是一個class,例項函式$onInit、$onPostLink和$onDestroy組成了新的生命週期鉤子,從字面上也容易理解了很多。$onInit代表編譯前,在這裡你可以對$scope做屬性變更,和邏輯定義;$onPostLink代表編譯後,這個時候子指令已完成了編譯,你可以在這裡獲取到任意子指令例項並呼叫其方法;$onDestroy在當前scope被銷燬時呼叫,在這裡你需要對dom做銷燬,以及可能的全域性變數做清空。很簡單就是元件編譯前、元件編譯後和元件銷燬時。那麼我們再看看react的生命週期鉤子。
react生命週期
react的生命週期分為三大階段,
- 插入dom(mount)
- 重新render(update)
- 銷燬dom(unmount)
在定義class(元件)的時候其實就是在定義一個狀態機,定義了各種狀態的響應,這裡包括這三大階段的鉤子以及事件響應,等待觸發條件的滿足。
1 插入dom(mount)
先來說下第一階段,首次render觸發時會執行以下鉤子函式,
- getDefaultProps
getDefaultProps: function(){ return { /* 預設屬性 */}; }複製程式碼
-
getInitialState
getInitialState: function(){ return { /* 初始狀態 */}; }複製程式碼
這兩個函式不難理解,目的是使程式碼結構更清晰,不需要在constructor裡做過多的打底處理。
-
componentWillMount
這個方法在出現class XX extends React.Component
之後其實就可以用constructor
來代替了。對於angular1.5這和$onInit
也是類似的,同樣也可以用constructor代替。 -
render
這個函式其實和angular指令中的template屬性(值對應一個函式)很相似,但是藉助jsx,render比template屬性強大實在太多,angular所提供的template不夠靈活,在template函式中無法拿到$scope,自然我們就選擇例如ngIf
這樣的屬性指令來做模版輸出控制,該指令會在當前指令作用域下再建立一個子作用域,這使得我們在獲取子元件的例項的時候會比較麻煩,ngRepeat
、ngSwitch
都是這樣,angular1的初衷是不希望我們自己做字串拼接,的確就算我們能拿到$scope,字串拼接也實在不夠優雅。
所以render函式直白點完成了兩件事情:- 執行jsx表示式,生成最終插入dom容器節點的真實dom(這裡不考慮虛擬dom的問題)
- 藉助react提供的synthetic event system完成事件監聽繫結
所有葉子元件的jsx模版都是由基礎原生html標籤、屬性以及react所提供的代理屬性組成的,因此如果你寫如下一段模版程式碼:
render(){ return ( <div num=1 changehandler={this.changeHandler.bind(this)}/> ) }複製程式碼
num
和changehandler
屬性會直接被忽略,如果你想在原生標籤上使用自定義屬性,請在屬性前面新增data-
。 -
componentDidMount
componentDidMount
和angular中的link或postLink很相似,但是它更讓人放心,因為angular中如果你想在link中獲取帶有ngIf或ngRepeat的dom是獲取不到的(因為會再transclude一次並等待下次髒資料校驗才會插入dom,有興趣的同學可以移步重複transclude和ngIf髒資料校驗),因此在這裡你可以放心的訪問dom。小結:上述方法除了
render
,只會伴隨第一次render執行一次。
2 重新render
當元件props和state(通過setState)發生變更時,會觸發re-render
操作。因此這裡我們把state和props變化分開來說。
在angular1中其實並沒有多次觸發的鉤子,像前面說到的controller、compile等也都只執行一次,其實react的重新渲染在angular類似$watch,一般我們在$watch來實現dom的變更,這種命令式的修改dom當然是不推薦的,react採用了一種更徹底的方式,給人的感受就像又走了一遍第一次渲染一樣,而你可以這樣理解,相對於第一次,它可能僅僅變更了個別state或props,通過虛擬dom和diff演算法,react幫助你高效的將變更應用於虛擬dom上最終re-render出新的dom樹。
2.1 state變更
狀態變更會觸發以下幾個鉤子
- shoudComponentUpdate
shouldComponentUpdate: function(nextProps, nextState){ return true }複製程式碼
該函式給使用者一個機會使得其可以控制是否需要進行重新渲染,angular1並沒有提供終止指令編譯的鉤子函式,這是react的高階特性了,主要針對效能優化,自己沒有用過就不誤導大家了。
-
componentWillUpdate
componentWillUpdate: function(nextProps, nextState){ // 為re-render做準備 }複製程式碼
為啥需要這個鉤子呢,因為只有
shouldComponentUpdate
返回true才能確定一定會重新渲染,那麼在這裡我們可以對介面的提示UI做變更,比如之前為了獲取state或props會因為非同步請求出現loading,那麼這個時候就可以隱藏loading。記住這裡不能有任何會再次觸發元件重新渲染的邏輯。 -
componentDidUpdate
componentDidUpdate: function(prevProps, prevState){ // 可以訪問渲染完成的dom }複製程式碼
這個沒啥可說的,和
componentDidMount
是一樣的。
2.2 props變更
componentWillReceiveProps: function(nextProps) {
this.setState({
// new state
});
}複製程式碼
不像state需要呼叫setState才會觸發re-render,由父元件導致的任何屬性變更都會觸發re-render,不需要呼叫額外的方法。其實props變更所經歷的生命週期鉤子和state幾乎一樣,唯一不同是在shouldComponentUpdate之前增加了componentWillReceiveProps,用意也十分明顯,我們可以在componentWillReceiveProps中通過setState對state做修改,比如元件內部的state是根據某個或多個props計算得出的,這樣我們在呼叫setState時候也不會造成額外的re-render被觸發,深入理解react
3 銷燬dom(unmount)
當我們改變UI佈局或者使用介面刪除元件樹上某個元件時就會觸發元件的componentWillUnmount
方法,在這裡你可以取消事件監聽或定時器,以及其他會造成全域性引用的變數。
class App extend Component {
componentWillUnmount(){
// unregister event or clear timer
}
}
render(<App />, document.getElementById(`root`)
function ummount(){
React.unmountComponentAtNode(document.getElementById(`root`));
}複製程式碼
4 結束
這篇其實和angular1的對比不是太多,但其實大概的生命週期和angular1還是大同小異,特別是re-render這塊更有條理也更有效能優勢。react與angular1的對比先暫告一段落,後面將開始redux的學習,同時會對官方的demo做詳細講解。
文中如有錯誤請大家及時糾正,謝謝。