玩轉Reactjs第四篇-組件(生命週期)

一、前言

     前一篇主要介紹了Reactjs組件的創建模式,瞭解了state以及props的基本知識和用法。本篇將重點介紹下Reactjs的生命週期這對於我們認識Reactjs的實現原理,正確使用鉤子函數至關重要。

   Reactjs在V16.3版本之前使用的是舊版本的生命週期,之後啓用了新版的生命週期,V17版本將徹底廢棄舊版本。我們將兩個版本都介紹下。

二、舊版本

     Reactjs生命週期包括掛載時(Mounting),更新時(Updateing),卸載時(Unmounting)三種場景,每種場景在不同狀態提供相關的鉤子函數,我們可以使用這些鉤子函數,實現自定義功能。

生命週期鉤子函數時序的示意圖如下:

1、掛載時(Mounting)

Reactjs組件首次加載時執行的流程,constructor->componentWillMount->render->componentDidMount。

  • constructor

調用時機:組件掛載前會調用構造函數。

鉤子作用:前一篇我們也講到,在構造方法裏,主要做以下兩件事:

(1)初始化state,這裏是唯一一個可以使用this.state進行初始化的地方,其他的地方只能使用setState更新。

(2)組件內定義的方法綁定實例,前一篇也講到如果不用箭頭函數定義方法,必須在構造器中綁定this。

如果不需要使用以上兩點,可以不定義組件的構造函數。

函數示例:示例如下:

constructor(props){
        // 1、調用父類構造方法
        super(props);
        //2、初始化state對象
        this.state = {hotItem:['口罩','手套','酒精',]};
        //綁定this
        this.changeHot = this.changeHot.bind(this);
}
  • componentWillMount(即將廢棄)

在方法即將廢棄,可以使用到React 17版本,但是會和新的組件生命週期鉤子函數衝突,建議不要使用。

調用時機:在調用render()前調用。

鉤子作用:在該方法裏面調用setState不會觸發額外的渲染,所以想要在該方法中請求外部數據,減少一次render,是一個常見的誤區,總的來說該方法沒有太大的作用。

  • render

調用時機:首次掛載以及更新時,都會調用該方法進行渲染。

鉤子作用:render方法是class組件中唯一需要實現的方法。在該鉤子方法裏,將React JSX渲染成DOM節點,並掛載到DOM樹中。

render函數應該是純的,意味着不應該改變組件的狀態,其每次調用都應返回相同的結果,同時它不會直接和瀏覽器交互。

函數示例:該方法的實現形式,前一篇我們也重點介紹了,如App.js中,實例如下:

render(){
    return (
    <div className="App">
      {/* 引入組件 */}
      <SearchBox changeSearchVal = {this.changeSearchVal}/>
      <SearchHot />
      <SearchList searchListVal={this.state.searchListVal}>
        <div>爲您搜索到2條記錄</div>
      </SearchList>
    </div>
    );
  }
  • componentDidMount

調用時機:執行完render函數(DOM節點已創建完成並掛載到DOM數中(節點插入DOM數)),立即執行。

鉤子作用:在componentDidMount中可以進行以下的操作:

(1)請求網絡數據,並調用setState進行更新,它將會觸發一次額外的渲染,但是它將在瀏覽器刷新屏幕之前發生。這保證了在此情況下即使render()將會調用兩次,用戶也不會看到中間狀態

(2)通過ref獲取DOM的節點,獲取DOM屬性或者操作DOM。

函數示例:在App.js中,我們增加componentDidMount方法,在該鉤子函數中獲取默認的搜索列表,這樣就能實現在首次加載完成後,頁面展示默認列表。

componentDidMount(){
    console.log("App componentDidMount");
    fetch("http://localhost:3000/search.json",{
      method:'GET',
    }).then(response => response.json())
    .then((data)=>{
      this.setState({searchListVal:data.search});
    })
  }

2、更新時(Updateing)

掛載完成後,當傳入父組件的props,以及組件內的state值發生變化,會觸發更新時流程。componentWillReceiveProps->shouldComponentUpdate->componentWillUpdate->render->componentDidUpdate

  • componentWillReceiveProps(nextProps)(即將廢棄)

該鉤子函數將在React 17版本廢棄。

調用時機:在已掛載的組件接收新的 props 之前被調用。

鉤子作用:當接收到新屬性後,通過if比較新舊Props不同,此方法中使用 this.setState() 執行 state 轉換。這個和新版本的getDerivedStateFromProps作用類似。

  • shouldComponentUpdate(nextProps,nextState)

調用時機:props或者state發送變化後,render調用前調用該鉤子函數。父組件更新prop,或者調用setState都會觸發。但是首次掛載以及forceUpdate不會觸發該方法。

鉤子作用:該函數可以決定是否繼續渲染(是否執行render方法),入參爲新的props和state,返回的是個布爾值,true表示繼續渲染,false則表示阻止渲染,默認返回true。

     在Vue中,通過監聽相關屬性值的變化,通過機制自動判斷是否需要渲染,無法"人爲"干預。在Reactjs中,利用shouldComponentUpdate爲我們提供了干預的契機,進行精確控制。

適用場景:在某些狀態或屬性變化時,通過this.props和nextProps以及this.state 和 nextState比較,來判斷是否需要重新渲染該組件。

函數示例:我們來改寫下SearchHot.js,當熱搜的個數有變化時,返回false,不繼續渲染。

shouldComponentUpdate(nextProps,nextState){
        console.log(nextState.hotItem.length);
        if(this.state.hotItem.length != nextState.hotItem.length){
            return false;
        }
        return true;
    }

由於state.hotItem新舊值的長度不一致,所以點擊"下一批",頁面不會有變化。

需要注意的是,state和props需要保持結構簡單,層次不要太深,不建議在 shouldComponentUpdate() 中進行深層比較或使用 JSON.stringify()。這樣非常影響效率,且會損害性能。

如果僅做淺層次比較,可以考慮使用React.PureComponent。

  • componentWillUpdate(nextProps, nextState)(即將廢棄)

該鉤子函數將在React 17版本廢棄,

調用時機:當組件收到新的 props 或 state 時,會在渲染之前調用。

鉤子作用:入參爲新的props以及state,使用此作爲在更新發生之前執行準備更新的機會,初始渲染不會調用此方法。

適用場景:可以讀取DOM屬性,爲componentDidUpdate作準備(使用場景少)。不能在此方法中調用 this.setState()

  • componentDidUpdate(prevProps, prevState, snapshot)

調用時機:在組件已經重新渲染之後調用,首次渲染不會執行此方法。

鉤子作用:入參爲前props,state值,第三個參數與新版本getSnapshotBeforeUpdate有關,下一節介紹。其作用類似componentDidMount,此時已經掛載完成,可以做DOM進行操作,也可以進行網絡請求。

使用場景:對DOM進行操作,比如滾動條指定到 位置,獲取DOM尺寸等。在該方法裏,可以調用setState,但是要包裹在一定的條件中,否則會 引起死循環。調用setSate會引起再一次的渲染,雖然用戶看不見(此時瀏覽器還沒有變化),但是會影響性能。

函數示例:   我們改寫下 SearchList.js,每次更新完,滾動條指向列表的指定位置。(這裏使用了ref獲取dom對象,後面會專門介紹)

componentDidUpdate(prevProps, prevState, snapshot){
    console.log("componentDidUpdate");
     //列表的滾動條指向60的位置
     this.searchul.current.scrollTop=60;
  }
...
render(){
 ...
 <ul className="u" ref={this.searchul}>
...
}

3、卸載時(Unmounting)

這個階段是指組件將被刪除並從DOM中清除。

  • componentWillUnmount()

調用時機:在組件卸載及銷燬之前直接調用。

適用場景:此方法中執行必要的清理操作,例如,清除 timer,取消網絡請求。不要在此方法中調用setState,因爲該組件永遠不會渲染。

三、新版本

V16.3版本啓用了新版本生命週期鉤子函數,不過此版本還可以兼容老版本的鉤子函數,與老版本一樣,也分三種場景。

生命週期的鉤子函數時序的示意圖如下:

與老版本比較,鉤子函數發送如下變化:

刪除:componentWillMount,componentWillReceiveProps,componentWillUpdate

新增:getDerivedStateFromProps,getSnapshotBeforeUpdate。

其他的函數保留。我們重點介紹下新增的。

  • static getDerivedStateFromProps(nextProps,preState)

調用時機:props以及state發送更新後,會在調用 render 方法之前調用發。與舊的生命週期圖比較可以看出,getDerivedStateFromProps處於原來的componentWillMountcomponentWillReceiveProps位置,setState以及forceUpdate方法的調用都會觸發該鉤子。

鉤子作用:從鉤子的函數名稱可以看出,用接受的props值來改變state值,返回一個新的state。它的入參是新props,舊state,該函數爲一個靜態的,這就表示無法使用組件實例(無法調用this)。

適用場景:組件分爲受控和非受控組件,受控是指數據由父組件的props傳入的組件(可以由父組件控制),非受控是指數據保存在內部的state的組件(外部無法直接控制)。如果state可以由props派生出,那麼就意味着該組件既是受控的,又是非受控的。其數據源就不唯一了,會導致一些意想不到的問題。通過props派生出state,是我們儘量需要避免的情況

官方文檔中,也爲我們羅列了以下兩種常見場景的替代方案,在你可能不需要使用派生 state這篇博文中進行了詳細的介紹。

(1)如果只想在 prop 更改時重新計算某些數據,請使用 memoization helper 代替。
(2)如果你想在 prop 更改時“重置”某些 state,請考慮使組件完全受控或使用 key 使組件完全不受控 代替。

函數示例:SearchList組件中,搜索的結果列表都是由父組件App通過props傳給該組件,下面我們修改下,在SearchList定義一個默認列表保存到state中,當父組件傳入的prop的列表結果更新後,修改state的值,實現更新。

constructor(props){
    super(props);
    this.searchul = React.createRef();
    //定義默認搜索列表
    this.state={searchListVal:['電腦','電視','冰箱']};
  }
  static getDerivedStateFromProps(nextProps,preState){
    //當props的列表發送更新,且與state的列表不一致,且不爲空
     if(nextProps.searchListVal!=preState.searchListVal&&nextProps.searchListVal.length > 0){
      //修改state的searchListVal值,返回新值
       return{
        searchListVal:nextProps.searchListVal
       }
     }
     //其他更新,state不變化
     return null;
  }
  • getSnapshotBeforeUpdate(prevProps, prevState)

調用時機:在最近一次渲染輸出(提交到 DOM 節點)之前調用。

鉤子作用:它讓你的組件能在當前的值可能要改變前獲得它們。這一生命週期返回的任何值將會 作爲參數被傳遞給componentDidUpdate(),即snapshot入參。

適用場景:適用的場景也不常用,它可能出現在 UI 處理中,如需要以特殊方式處理滾動位置的聊天線程等。

函數示例:在SearchList組件中,我們通過componentDidUpdate,每次加載列表,將滾動條滾動到指定的位置。下面我們再次修改下,獲取滾動條之前的位置,在此基礎上再累加一個指定值,模擬懶加載的情況下,每次顯示最新加載的值(列表底部)。

getSnapshotBeforeUpdate(prevProps, prevState){
    //獲取當前滾動條位置
    return this.searchul.current.scrollTop;
  }
  componentDidUpdate(prevProps, prevState, snapshot){
    //在滾動條位置上,累加60個
    if(snapshot!=null){
      this.searchul.current.scrollTop=snapshot+60;
    } 
  }

四、與Vue的生命週期比較

這裏我們將Reactjs的生命週期與Vue的比較下。

1、掛載時

2、更新時

3、銷燬時

五、總結

本章節主要描述了Reactjs的新舊生命週期以及相關鉤子函數的調用時機,功能,以及適用場景,並與vue的生命週期做了比較。

老版本的生命週期鉤子函數的時序:

1、掛載時,constructor->componentWillMount->render->componentDidMount

2、更新時,componentWillReceiveProps->shouldComponentUpdate->componentWillUpdate->render->componentDidUpdate

3、卸載時,componentWillUnmount

新版本的生命週期鉤子函數的時序:

1、掛載時,constructor->getDerivedStateFromProps->render->componentDidMount

2、更新時,getDerivedStateFromProps->shouldComponentUpdate->render->componentDidUpdate

3、卸載時,componentWillUnmount

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章