React生命週期與常見使用場景

前言

對於react的生命週期一直是在工作中很重要的,我們往往需要在需要的時間段做自己需要的事情,在 React 中,生命週期大致上可以分爲初始化(Initialization)、掛載(Mounting)、更新(Updating)和卸載(Unmounting) 這幾個階段,每個階段又會分別調用不同的生命週期函數。當然隨着react版本的更新,也會出現相對的變化,所以我們現在來參考總結下

大部分團隊不見得會跟進升到16版本,所以16前的生命週期還是很有必要掌握的,何況16也是基於之前的修改
在這裏插入圖片描述

一 初始化階段 Initialization

也就是以下代碼中類的構造方法( constructor() ),Test類繼承了react Component這個基類,也就繼承這個react的基類,纔能有render(),生命週期等方法可以使用,這也說明爲什麼 函數組件不能使用這些方法的原因。super(props) 用來調用基類的構造方法( constructor() ), 也將父組件的props注入給子組件,功子組件讀取(組件中props只讀不可變,state可變)。 而 constructor() 用來做一些組件的初始化工作,如定義this.state的初始內容。

注意:
constructor不算生命週期函數。constructor我們叫構造函數,它是ES6的基本語法。雖然它和生命週期函數的性質一樣,但不能認爲是生命週期函數。但是你要心裏把它當成一個生命週期函數,我個人把它看成React的Initialization階段,定義屬性(props)和狀態(state)。

import React, { Component } from 'react'
class Test extends Component {
  constructor(props) {
  super(props)
  }
}

二 掛載階段 Mounting

  1. componentWillMount: 在組件即將被掛載到頁面的時刻執行。

在組件掛載到DOM前調用,且只會被調用一次,在這邊調用this.setState不會引起組件重新渲染,也可以把寫在這邊的內容提前到constructor()中,所以項目中很少用。

  1. render: 頁面state或props發生變化時執行。

根據組件的props和state(無兩者的重傳遞和重賦值,論值是否有變化,都可以引起組件重新render) ,return一個React元素(描述組件,即UI),不負責組件實際渲染工作,之後由React自身根據此元素去渲染出頁面DOM。render是純函數(Pure function:函數的返回結果只依賴於它的參數;函數執行過程裏面沒有副作用),不能在裏面執行this.setState,會有改變組件狀態的副作用。

  1. componentDidMount: 組件掛載完成時被執行。

組件掛載到DOM後調用,且只會被調用一次

export default class App extends Component {
  componentWillMount () {
    console.log('componentWillMount----組件將要掛載到頁面的時刻')
  }
  render() {
    console.log('render---組件掛載中.......')
    return (
      <div>
        <TodoList/> 
        <ReduxTodoList/>
      </div>
    )
  }
  componentDidMount () {
    console.log('componentDidMount----組件掛載完成的時刻執行')
  }
}

打印的結果:

componentWillMount----組件將要掛載到頁面的時刻執行
render----開始掛載渲染
componentDidMount----組件掛載完成的時刻執行

這就是掛載階段的生命週期的執行順序,當然他跟書寫順序沒有關係

注意:
componentWillMountcomponentDidMount這兩個生命週期函數,只在頁面刷新時執行一次,而render函數是隻要有state和props變化就會執行,這個初學者一定要注意。

三 更新階段 Updation

這個階段是較爲複雜的階段,由上面的圖可以看出造成組件更新有兩類的情況,需要先明確下React組件更新機制。setState引起的state更新或父組件重新render引起的props更新,更新後的state和props相對之前無論是否有變化,都將引起子組件的重新render

  1. shouldComponentUpdate在組件更新之前,自動被執行,它要求返回一個布爾類型的結果,必須有返回值,這裏就直接返回一個true了(真實開發中,這個是有大作用的,能有有效的解決性能的問題),如果你返回了false,這組件就不會進行更新了。 簡單點說,就是返回true,就同意組件更新;返回false,就反對組件更新。此方法通過比較nextProps,nextState及當前組件的this.props,this.state,返回true時當前組件將繼續執行更新過程,返回false則當前組件更新停止,以此可用來減少組件的不必要渲染,優化組件性能。

  2. componentWillUpdate在組件更新之前,但shouldComponenUpdate之後被執行,但是shouldComponenUpdate返回的是false的時候就會反對組件的更新,這個函數就不會執行

  3. componentDidUpdate在組件更新之後執行,它是組件更新的最後一個環節

export default class App extends Component {
  shouldComponentUpdate () {
    console.log ('1.shouldComponentUpdate----組件更新之前')
    return true
  }
  componentWillUpdate () {
    console.log ('2.componentWillUpdate---組件更新前,shouldComponentUpdate函數之後執行 ')
  }
  render() {
    console.log('3.render---組件掛載渲染.......')
    return (
      <div>
        <TodoList/> 
        <ReduxTodoList/>
      </div>
    )
  }
  componentDidUpdate () {
    console.log('componentDidUpdate----組件更新完成的時刻執行')
  }
}

打印結果與執行順序

1-shouldComponentUpdate---組件發生改變前執行
2-componentWillUpdate---組件更新前,shouldComponentUpdate函數之後執行
3-render----開始掛載渲染
4-componentDidUpdate----組件更新之後執行
  1. 那麼上圖中的componentWillReceiveProps在什麼時候執行呢,其實子組件接收到父組件傳遞過來的參數,父組件render函數重新被執行,這個生命週期就會被執行。
    注意:
    使用componentWillReceiveProps組件第一次存在於Dom中,函數是不會被執行的;
    如果已經存在於Dom中,函數纔會被執行。
    當父組件傳遞的 props 即將引起組件更新時會被調用,該方法接受一個參數指的是當前父組件傳遞給組件的最新的 props 狀態數據。在這個生命週期方法中,我們可以根據比較 nextProps 和 this.props 新舊 props 的值查明 props 是否改變,依次做一些數據處理的邏輯。

銷燬階段 Unmounting

這個階段就只一個生命週期函數:componentWillUnmount,此方法在組件被卸載前調用,可以在這裏執行一些清理工作,比如清楚組件中使用的定時器,清楚componentDidMount中手動創建的DOM元素、解綁事件等,以避免引起內存泄漏

總結利用生命週期函數的優化頁面:

  1. componentDidMount生命週期函數裏請求ajax,建議在componentDidMount函數裏執行,因爲在render裏執行,會出現很多問題,比如一直循環渲染;在componentWillMount裏執行,在使用RN時,又會有衝突。所以強烈建議在componentDidMount函數裏作ajax請求。
  2. 在父組件更新的時候傳遞到子組件的時候導致pros反正改變,子組件的render函數不停觸發,出現性能問題,我們可以這個一般發生在更新階段,所以利用shouldComponentUpdate
shouldComponentUpdate(nextProps,nextState){
    if(nextProps.content !== this.props.content){
        return true
    }else{
        return false
    }

}

React v16.4 的生命週期

在這裏插入圖片描述

變更緣由

原來(React v16.0前)的生命週期在React v16推出的Fiber之後就不合適了,因爲如果要開啓async rendering,在render函數之前的所有函數,都有可能被執行多次。
原來(React v16.0前)的生命週期有哪些是在render前執行的呢?

  • componentWillMount
  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate

如果開發者開了async rendering,而且又在以上這些render前執行的生命週期方法做A JAX請求的話,那AJAX將被無謂地多次調用。。。明顯不是我們期望的結果。而且在componentWillMount裏發起AJAX,不管多快得到結果也趕不上首次render,而且componentWillMount在服務器端渲染也會被調用到(當然,也許這是預期的結果),這樣的IO操作放在componentDidMount裏更合適。

禁止不能用比勸導開發者不要這樣用的效果更好,所以除了shouldComponentUpdate,其他在render函數之前的所有函數(componentWillMountcomponentWillReceiveProps,componentWillUpdate)都被getDerivedStateFromProps替代。

也就是用一個靜態函數getDerivedStateFromProps來取代被deprecate的幾個生命週期函數,就是強制開發者在render之前只做無副作用的操作,而且能做的操作侷限在根據props和state決定新的state

新引入了兩個新的生命週期函數:

1. getDerivedStateFromProps

getDerivedStateFromProps 本來(React v16.3中)是隻在創建和更新(由父組件引發部分),也就是不是不由父組件引發,那麼getDerivedStateFromProps也不會被調用,如自身setState引發或者forceUpdate引發
這樣的話理解起來有點亂,在React v16.4中改正了這一點,讓getDerivedStateFromProps無論是Mounting還是Updating,也無論是因爲什麼引起的Updating,全部都會被調用,具體可看React v16.4 的生命週期圖。
getDerivedStateFromProps(props, state) 在組件創建時和更新時的render方法之前調用,它應該返回
一個對象來更新狀態,或者返回null來不更新任何內容

每當父組件引發當前組件的渲染時,getDerivedStateFromProps 會被調用,這樣我們有機會可以根據新的 props 和之前的 state 來調整新的 state。如果放在三個被 deprecate 生命週期函數中實現比較純,沒有副作用的話,就可以搬到 getDerivedStateFromProps 了;如果不幸做了類似 AJAX 之類的操作,首先要反省爲什麼自己當初這麼做,然後搬到 componentDidMount 或者 componentDidUpdate 中去。目前當你使用三個被 deprecate 生命週期函數時,開發模式下會有紅色警告,要求你使用 UNSAFE_ 前綴。可能會在打一次大版本更新時直接廢棄,所以那些抱有僥倖心理的開發者還是放棄使用吧。

2. getSnapshotBeforeUpdate

getSnapshotBeforeUpdate() 被調用於render之後,可以讀取但無法使用DOM的時候。它使您的組件可以在可能更改之前從DOM捕獲一些信息(例如滾動位置)。此生命週期返回的任何值都將作爲參數傳遞給componentDidUpdate()。
官網給的例子:

class ScrollingList extends React.Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
  }
  getSnapshotBeforeUpdate(prevProps, prevState) {
    //我們是否要添加新的 items 到列表?
    // 捕捉滾動位置,以便我們可以稍後調整滾動.
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }
  componentDidUpdate(prevProps, prevState, snapshot) {
    //如果我們有snapshot值, 我們已經添加了 新的items.
    // 調整滾動以至於這些新的items 不會將舊items推出視圖。
    // (這邊的snapshot是 getSnapshotBeforeUpdate方法的返回值)
    if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }
  render() {
    return (
      <div ref={this.listRef}>{/* ...contents... */}</div>
    );
  }
}

注意:
當你同時使用了 getDerivedStateFromProps、 getSnapshotBeforeUpdate 新的生命週期 API 和 deprecate 生命週期函數時,deprecate 生命週期函數會被直接忽略掉,並不會適時執行!

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