1. 程式人生 > 其它 >React 的生命週期

React 的生命週期

技術標籤:基礎回顧es6reactjs

企業專案實戰 > 第二部分 > React 基礎回顧

React 的生命週期

關於生命週期的一些時間節點(簡單瞭解即可)

  • 2018 年 3 月, 16.3 版本更新, 對生命週期函式做出了比較大的調整, 出現了兩個新的生命週期函式 getDerivedStateFromProps 與 getSnapshotBeforeUpdate, 並宣告將逐漸廢棄 componentWillMount、componentWillReceiveProps、componentWillUpdate

  • 2018 年 5 月, 16.4 版本更新, 再次修正並確認了新的生命週期;

  • 2019 年 2 月, hooks 出現, 部分比較前衛的小公司新開專案, 都直接上了 hooks, 但大公司因為舊的專案太多, 所以還在類元件上折騰著, 只有少數比較前衛的團隊開始使用 hooks;

react 更新日誌, 感興趣的同學可以課後去看一看, 瞭解一下就行, 不用花太多時間, 主要是你們工作以後, 需要關注這塊以瞭解最近的更新有了哪些新特性或者有哪些特性被宣佈被廢棄, 這些都是很重要的, 關係著你在寫專案時可以使用哪些方法不能使用哪些方法, 比如生命週期, 現在還有很多大公司因為早期的專案中 16.4 以前的程式碼太多, 無法使用最新的 react 版本。

https://github.com/facebook/react/blob/master/CHANGELOG.md

什麼是生命週期

這個問題已經問得比較濫了, 其實生命週期(Life Cycle)的概念應用很廣泛, 特別是在政治、經濟、環境、技術、社會等諸多領域經常出現, 其基本涵義可以通俗地理解為“從搖籃到墳墓”(Cradle-to-Grave)的整個過程。而對於 React 來講, 應該稱之為元件的生命週期, 這樣可以從字面理解, 就是一個元件從載入到更新再到解除安裝的整個流程。

React 元件生命週期函式有哪些

16.3 以前的生命週期

參考下面的圖: 早期的生命週期函式比較多, 分以下 4 個階段: 建立階段、例項化階段、更新階段、解除安裝階段

  • 建立階段

getDefaultProps(16.4 後廢棄)

這個階段只會觸發一個 getDefaultProps 方法, 該方法返回一個物件並快取起來。然後與父元件指定的 props 物件合併, 最後賦值給 this.props 作為該元件的預設屬性。

  • 例項化階段

該階段主要發生在例項化元件類的時候, 也就是該元件類被呼叫的時候觸發。這個階段會觸發一系列的流程, 按執行順序如下:

getInitialState(16.4 後廢棄): 初始化元件的 state 的值。其返回值會賦值給元件的 this.state 屬性。
componentWillMount(16.4 後廢棄): 這裡可以根據業務邏輯來對 this.state 進行一些相應的操作。
render: 根據 this.state 的值, 生成頁面需要的虛擬 DOM 結構, 並返回該結構。
componentDidMount: 在 render 完成後觸發, 這個時候已經可以通過 ReactDOM.findDOMNode(this) 來獲取當前元件的節點, 然後就可以像 Web 開發中那樣操作裡面的 DOM 元素了。

  • 更新階段

這主要發生在使用者操作之後或者父元件有更新的時候, 此時會根據使用者的操作行為進行相應的頁面結構的調整。這個階段也會觸發一系列的流程, 按執行順序如下:

componentWillReceiveProps(16.4 後廢棄): 當元件接收到新的 props 時, 會觸發這個函式。在這個函式中, 通常可以呼叫 this.setState 方法來完成對 this.state 的修改。
shouldComponentUpdate: 該方法用來攔截新的 props 或 state, 然後根據事先設定好的判斷邏輯, 做出最後要不要更新元件的決定。
componentWillUpdate(16.4 後廢棄): 當上面的方法攔截返回 true 的時候, 就可以在該方法中做一些更新之前的操作。
render: 根據一系列的 diff 演算法, 生成需要更新的虛擬 DOM 資料。(在 render 中只允許進行資料和模板的組合, 不允許對 state 進行修改, 一是維護程式碼可讀性, 最關鍵的是避免因為修改屬性而造成的死迴圈)
componentDidUpdate: 該方法在元件的更新已經同步到真實 DOM 後觸發, 我們常在該方法中做一些 DOM 操作。

  • 解除安裝階段

componentWillUnmount

當元件需要從 DOM 中移除的時候, 我們通常會做一些取消事件繫結、移除虛擬 DOM 中對應的元件資料結構、銷燬一些無效的定時器等工作。這些事情都可以在這個方法中處理

class Test extends Readt.component {
  /* 1.建立階段 */
  //在建立類的時候被呼叫
  getDefaultProps: function() {
    console.log("getDefaultProps");
    return {};
  },

  /* 2.例項化階段 */
  //獲取this.state的預設值
  getInitialState: function() {
    console.log("getInitialState");
    return {name: "hangge.com"};
  },
  //元件將要載入, 在render之前呼叫此方法
  componentWillMount: function() {
    //業務邏輯的處理都應該放在這裡, 比如對state的操作等
    console.log("componentWillMount");
  },
  //渲染並返回一個虛擬DOM
  render: function() {
    console.log("render");
    return (
            <div>歡迎訪問: {this.state.name}</div>
    );
  },
  //元件完成載入, 在render之後呼叫此方法
  componentDidMount: function() {
    //在該方法中, ReactJS會使用render方法返回的虛擬DOM物件來建立真實的DOM結構
    console.log("componentDidMount");
    var node = ReactDOM.findDOMNode(this);
    console.log(node);
  },

  /* 3.更新階段 */
  //該方法發生在this.props被修改或父元件呼叫setProps()方法之後
  componentWillReceiveProps: function() {
    console.log("componentWillRecieveProps");
  },
  //是否需要更新
  shouldComponentUpdate: function() {
    console.log("shouldComponentUpdate");
    return true;
  },
  //將要更新
  componentWillUpdate: function() {
    console.log("componentWillUpdate");
  },
  //更新完畢
  componentDidUpdate: function() {
    console.log("componentDidUpdate");
  },

  /* 4.銷燬階段 */
  //銷燬時會被呼叫
  componentWillUnmount: function() {
    console.log("componentWillUnmount");
  },
}

注意: 如果你是 3~4 年的工作經驗的話, 也就是說到你畢業, 應該是 2021 年年初, 往前回溯 4 年, 應該是 2017 年左右, 那麼相對來說早期的生命週期不需要記太多, 面試官如果問起, 可以說很長時間沒有用到了, 基本忘記了, 因為你使用的時間太短, 記不住是允許的。如果你的簡歷是在 2016 年就開始工作的話, 需要記下來一部分, 至少要把後面標紅的這些記下來。

但是接下來的 16.3 以後的生命週期需要記一下, 因為時間點比較近, 對比國內的生產環境, 應該還有很多公司在用著。哪怕是你說你 hooks 出現以後就直接沒再使用生命週期也不可能全忘記的。

  • 16.3 以後的生命週期

React 16.3 時, 對生命週期函式做出了比較大的調整, 出現了兩個新的生命週期函式 getDerivedStateFromProps 與 getSnapshotBeforeUpdate, 並宣告將因為安全問題,即將廢棄 componentWillMount、componentWillReceiveProps、componentWillUpdate 這三個生命週期

參考下圖, 新的生命週期被精簡為三個階段: 掛載階段、更新階段以及解除安裝階段

從圖中我們可以看到, 新版的生命週期引入了兩個全新的生命週期函式: getDerivedStateFromProps, getSnapShotBeforeUpdate

getDerivedStateFromProps: 這個生命週期函式代替了早期的 componentWillReceiveProps 和 componentWillMount, 也就是說無論是掛載階段還是更新階段全部都會呼叫。

// 早期的
componentWillReceiveProps(nextProps) {
  if (nextProps.isLogin !== this.props.isLogin) {
    this.setState({
      isLogin: nextProps.isLogin,
    });
  }
  if (nextProps.isLogin) {
    this.handleClose();
  }
}
// 新版的
static getDerivedStateFromProps(nextProps, prevState) {
  // 判斷最傳入的props中的值是否與現有的state中的值相同,如果相同返回空,如果不同則將新接收到的props中的值覆蓋當前state裡的值
  if (nextProps.isLogin !== prevState.isLogin) {
    return {
      isLogin: nextProps.isLogin,
    };
  }
  return null;
}
componentDidUpdate(prevProps, prevState) {
  // 這裡的prevProps
  if (!prevState.isLogin && this.props.isLogin) {
    this.handleClose();
  }
}

getSnapshotBeforeUpdate: 這個生命週期函式被放置在了 render 之後, 可以讀取但無法使用 DOM 的時候。它使得我們的元件可以在可能更改之前就能從 DOM 中獲得一些資訊, 比如滾動位置。這個生命週期函式裡的任何值都將會作為引數傳遞給 componentDidUpdate。

來自官網的 getSnapshotBeforeUpdate 的例子

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>;
  }
}

關聯閱讀

  • React 元件的生命週期有三個不同的階段

總結:

如果有面試官問起這個問題, 你可以直接描述 16.4 以後版本的生命週期, 如果面試官問起你更多的生命週期, 那麼, 你可以把 WillMount、WillUpdate 這兩個方法也簡單說下, 甚至可以說說它們為什麼會被棄用, 這應該可以給你加分

掛載階段:

  • constructor: React 在這裡完成對資料的初始化,它接受兩個引數:props 和 context,當想在函式內部使用這兩個引數時,需使用 super()傳入這兩個引數
  • render: React 在這裡將你寫入的 JSX 生成一個虛擬 Dom 樹,然後渲染到頁面中;
  • componentDidMount: 元件第一次渲染完成,這個時候 DOM 節點已經生成,我們可以在這裡以 refs 的方式獲取元素或者呼叫 ajax 請求,返回資料 setState 後元件會重新渲染

更新階段:

  • getDerivedStateFromProps: 它提供了兩個引數: nextProps 與 prevState;舊版本的 componentWillReceiveProps 方法需要在這裡判斷前後兩個 props 是否相同,如果不同再將新的 props 更新到相應的 state 上去,這樣就破壞了 state 資料的單一資料來源特性,導致元件狀態變得不可控;在這個方法中,我們將不再有權訪問 this.props,而只能通過比較 nextProps 與 prevState 的值來
  • shouldComponentUpdate:
  • render: 這裡就是上面掛載階段的 Render 方法,只不過在每一次元件更新時,React 會在這裡通過其 diff 演算法比較更新前後的新舊 DOM 樹,比較以後,找到最小的有差異的 DOM 節點,並重新渲染;
  • getSnapshotBeforeUpdate: 生命週期走到這裡,代表著整個 DOM 已經生成完成但還沒有渲染到頁面中。我們可以在這裡讀取當前某個 DOM 元素的狀態,並在 componentDidUpdate 中進行相應的處理。
  • componentDidUpdate: 與掛載階段的 componentDidMount 幾乎一致,唯一的區別是 componentDidMount 只會在元件首次渲染時被觸發,而頁面每次被更新後都會觸發 componentDidUpdate

解除安裝階段:

  • componentWillUnmount: 這個與舊版完全一致,元件被解除安裝前觸發,我們要在這將所有的事件監聽、計時器等統統幹掉