angular1與react生命週期對比

shineYao發表於2019-03-04

能否理解一個元件的生命週期是非常重要的,甚至可以說是最重要的內容,他是一切進階的基石,下面我們就從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這樣的屬性指令來做模版輸出控制,該指令會在當前指令作用域下再建立一個子作用域,這使得我們在獲取子元件的例項的時候會比較麻煩,ngRepeatngSwitch都是這樣,angular1的初衷是不希望我們自己做字串拼接,的確就算我們能拿到$scope,字串拼接也實在不夠優雅。
    所以render函式直白點完成了兩件事情:

    • 執行jsx表示式,生成最終插入dom容器節點的真實dom(這裡不考慮虛擬dom的問題)
    • 藉助react提供的synthetic event system完成事件監聽繫結

    所有葉子元件的jsx模版都是由基礎原生html標籤、屬性以及react所提供的代理屬性組成的,因此如果你寫如下一段模版程式碼:

    render(){
      return (
          <div num=1 changehandler={this.changeHandler.bind(this)}/>
      )
    }複製程式碼

    numchangehandler屬性會直接被忽略,如果你想在原生標籤上使用自定義屬性,請在屬性前面新增data-

  • componentDidMount

    componentDidMount和angular中的link或postLink很相似,但是它更讓人放心,因為angular中如果你想在link中獲取帶有ngIf或ngRepeat的dom是獲取不到的(因為會再transclude一次並等待下次髒資料校驗才會插入dom,有興趣的同學可以移步重複transcludengIf髒資料校驗),因此在這裡你可以放心的訪問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做詳細講解。

文中如有錯誤請大家及時糾正,謝謝。

相關文章