【詳解】 React 組件生命週期 API 及 useEffect Hook 新特性方案


本文篇幅較長,如果你想要:

  1. 完整了解整個組件生命週期的變更和對應函數API,那麼請從頭開始;
  2. 快速理解傳統生命週期函數API的含義和使用,那麼點擊目錄第二部分;
  3. 快速瞭解如何在函數組件中使用useEffect來替代原有的生命週期API,那麼點擊目錄第三部分。

一、 引言

如果你是一名前端開發者,並且使用React開發框架,那麼你一定繞不開React的一個核心概念——組件生命週期。 組件的生命週期描述的是一個組件從創建,渲染,加載,顯示到卸載的整個過程。其大致過程如下:
圖一 React 組件生命週期圖(v16.4)

圖片來源請戳此鏈接:http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

我們從上圖可以得知,一個組件主要包括三種行爲,分別是:創建,更新,卸載。每個行爲又可以劃分爲:Render 渲染階段和 Commit 階段 (更新時存在Pre-Commit 階段)。這裏先簡單瞭解一下生命週期的概況,後文會進行詳細描述。

React 官方開發者在生命週期的每個階段都提供了相關的API,我們可以使用這些API定義組件在某個生命週期進行執行相關的行爲。比較常用的就是使用componentDidMount API,在該函數內部獲取來自服務器的數據。

學習這些API函數的使用並瞭解組件的生命週期的運行機制,是一個React開發者的基本功

但是,我們必須不能只注重 API 的使用,而忽略了對生命週期的過程理解,API 只是 API,隨時都有可能被替代!

這不,在 React v16.8 版本中,橫空出世的 Hooks (俗稱鉤子)顛覆了之前的類組件範式,讓函數組件逐漸成爲主流,越來越多的團隊開始考慮基於Hooks進行項目的重構,過去那種在類組件中調用各種生命週期API函數的方法將會慢慢成爲過去(不過現在還是得學呀…)

本文不打算進行詳述Hooks的用法及優越性,已經有許多優秀的前端團隊對其展開了極富深度的論述啦,我只是一個剛入門不久的前端菜鳥,暫時難以從高層的視角來評判,如果你感興趣,本文在第四部分推薦閱讀給出一些高質量的鏈接,你可以學到更多。

回到主題。Hooks 提供了一套新的闡釋組件生命週期的方式 ,原先許多生命週期的 API 都有了對應的 Hooks 解決方案——使用強大的useEffect這一Hooks函數

綜上,本文主要關注以下內容:

  1. 類組件生命週期理解和API用法;
  2. 如何在函數組件中使用useEffect來替代類組件的生命週期API。

攥寫本文的初衷是,本人最近在經常使用React框架開發,對生命週期這一核心概念有必要進行一番梳理,同時也發現網上關於講解如何使用新特性Hooks來定製生命週期行爲的博文數目較少而且不全面,所以我花費幾個小時的時間來整理這些概念,總結方法並提供具體的代碼實例,希望能幫助到大家,互相學習。


二、 組件生命週期過程及API詳解

這裏先放我從谷歌搜到的一張大家都在用的生命週期圖(圖源見鏈接),直接從API入手描述整個組件的生命週期過程,如果你已經使用過相關的API,那相當明瞭,但考慮到初學者,我還是基於個人理解給大家歸納總結一下,幫助大家理解、記憶和使用。
組件生命週期API調用流程圖
本文將API歸爲三類:

  1. Mount:掛載API
  2. Update: 更新API
  3. Unmount:卸載API

結合上述流程圖,我們從這三類API開始展開敘述,簡單講解一些不常用的API (不完全覆蓋),多把文字放在常用的API(基本覆蓋)上。

2.1 Mount

掛載一個組件的過程是先實例化創建該組件並渲染,然後讀取DOM使得組件運行在瀏覽器中。整個過程涉及到的 API 函數如下:
在這裏插入圖片描述
當組件在客戶端實例化首次創建時,以下方法依次被調用:

  1. getDefaultProps(ES5語法,過時)
  2. getInitialState(ES5語法,過時)
  3. componentWillMount(棄用)
  4. render(必須使用)
  5. componentDidMount(常用)

當組件屬於服務端渲染時,以下方法依次被調用:

  1. getDefaultProps
  2. getInitialState
  3. componentWillMount
  4. render

服務端渲染沒有 componentDidMount 方法時因爲其執行階段是組件掛載(插入DOM樹)之後,纔會執行,發生在客戶端。

但需要提醒的是,getDefaultPropsgetInitialState 方法僅適用於非ES6語法情況,在ES6語法中我們使用 constructor函數來代替 getInitialState 函數,而getDefaultProps則可以通過手動賦值指定props屬性,但已不屬於函數方法了。(來源官方文檔不適用ES6),但是考慮到可能還有些人依舊會使用ES5語法,這裏也會簡單介紹這兩個函數。(後續若非特別指明,否則討論範圍都在ES6語法之內)

同時在圖一,我們可以得知在constructor之後,render之前會有一個getDerivedStateFromProps函數,這個函數在掛載和更新階段都有可能調用,我們放在update部分來講。

所以下文我將介紹上述6個函數,包括函數的作用,方法和一些注意事項。

2.1.1 constructor

這是目前常用的一個函數,我們編寫的React 類組件都是React.Component基類的繼承,組件進行實例化的時候,都會調用其構造函數;如果你不初始化 state,不綁定方法的話,你不需要爲 React 組件實現構造函數(它會調用默認構造函數)。

而且在構造函數的開頭,你需要調用 super(props), 不然會有許多蜜汁bug難以定義和識別;

通常構造函數的作用就是:

  1. 初始化內部state變量
  2. 爲事件處理函數綁定實例
    需要注意的時,在構造函數裏不要出現setState方法,我們僅需要爲 this.state 賦初始值即可。

一個常見的例子如下:

// credit to https://react.docschina.org/docs/react-component.html#constructor
constructor(props) {
  super(props);
  // 不要在這裏調用 this.setState()
  this.state = { counter: 0 };
  this.handleClick = this.handleClick.bind(this);
}

2.1.2 getDefaultProps

這種方法其實只有在使用非ES6語法定義組件(即採用ES5語法 React.createClass定義組件)的時候纔會出現,這是一種聲明默認組件屬性(props)的方法。該函數只會調用一次。
一個簡單的例子:

// credit to https://www.cnblogs.com/MuYunyun/p/6629096.html#_lab2_0_0
var MyTitle = React.createClass({
  getDefaultProps : function () {
    return {
      title : 'Hello World'  // 聲明默認的屬性title
    };
  },

  render: function() {
     return <h1> {this.props.title} </h1>;  // 調用props屬性
   }
});

ReactDOM.render(
  <MyTitle />,
  document.body
);

而在ES6語法中,這個函數已經被棄用了,起同樣作用的寫法如下:

// credit to 
// https://zh-hans.reactjs.org/docs/react-without-es6.html#setting-the-initial-state
class Greeting extends React.Component {
  // ...
}

Greeting.defaultProps = {
  name: 'Mary'
};

2.1.3 getInitialState

ES5語法中,這個函數的作用相當於初始化每個組件實例的state變量;在ES6語法中,我們可以通過在constructor函數裏頭對this.state賦初始值,以起到同樣的效果。
getInitialStategetDefaultProps的區別在於,getDefauProps 對於組件類來說只會調用一次,而getInitalState是在每個組件實例化的時候都會調用,並且只調用一次。而props和state的區別在於props是通過父組件傳遞,在所有實例中共享,而state只存在組件內部,保存組件的某些狀態。
這裏提供一個代碼實例幫助理解:

var LikeButton = React.createClass({
  getInitialState: function() {
    return {liked: false};
  },
  handleClick: function(event) {
    this.setState({liked: !this.state.liked});
  },
  render: function() {
    var text = this.state.liked ? 'like' : 'haven\'t liked';
    return (
      <p onClick={this.handleClick}>
        You {text} this. Click to toggle.
      </p>
    );
  }
});

ReactDOM.render(
  <LikeButton />,
  document.getElementById('example')
);

2.1.4 componentWillMount

這個函數在進行組件渲染之前調用,這個函數內部進行setState時,並不會觸發額外的渲染(合併到在下一步的render中執行)。因爲此方法在組件的生命週期中只調用一次,而這恰好發生在組件初始化之前。因此,無法訪問DOM

當你使用服務端渲染的時候,這是服務器端惟一調用的生命週期方法。

不過這個函數在較新版本的React中已經被視爲不安全(unsafe),官方建議我們不使用該函數。詳見(UNSAFE_componentWillMount())。
這個方法是特別不常用並且不建議使用的。僅提供一個簡單實例:

 componentWillMount() {
    let mode;
    if (this.props.age > 70) {
      mode = 'old';
    } else if (this.props.age < 18) {
      mode = 'young';
    } else {
      mode = 'middle';
    }
    this.setState({ mode });
  }

2.1.5 render

這個方法是最常用,而且在類組件中是必用的方法。該方法用於創建一個虛擬DOM,用來表示組件的結構。需要注意幾點的就是:

  1. 只能通過propsstate來訪問數據,不能修改;
  2. 支持返回null,false或其他react組件;
  3. 只能返回一個頂級組件,如果出現多個組件,你需要用<div>標籤變成一個組件;
  4. 無法改變組件的狀態,class組件中只能通過setState方法改變。

這裏不給出實例,見上面代碼即可。

2.1.6 componentDidMount

當組件掛載(Mount)DOM樹上但並未顯示到瀏覽器上時,這個函數方法將會被立即調用。
一些依賴於DOM節點初始化的操作應該要放在這裏,如常見的向服務器請求數據。

我們通常在這個函數內部獲取數據,然後通過setState的方法觸發額外的渲染,也就是說從構造到瀏覽器運行組件可能會觸發兩次的render,但是用戶並不會看到中間的狀態,因爲此時的瀏覽器並未更新屏幕。

不過這個方法內部的數據請求過於龐大可能會引發性能問題,需要謹慎使用。

雖然我們也可以在componentWillMount函數中請求數據,但是官方推薦我們使用這個函數進行數據的異步請求。詳細看官方解釋fetching-external-data

我摘取其重要部分代碼和內容,濃縮如下:
先看兩個函數的使用實例。

// Before 使用componentWillMount
class ExampleComponent extends React.Component {
  state = {
    externalData: null,
  };

  componentWillMount() {
    this._asyncRequest = loadMyAsyncData().then(
      externalData => {
        this._asyncRequest = null;
        this.setState({externalData});
      }
    );
  }

  componentWillUnmount() {
    if (this._asyncRequest) {
      this._asyncRequest.cancel();
    }
  }

  render() {
    if (this.state.externalData === null) {
      // Render loading state ...
    } else {
      // Render real UI ...
    }
  }
}
// After 使用componentDidMout
class ExampleComponent extends React.Component {
  state = {
    externalData: null,
  };

  componentDidMount() {
    this._asyncRequest = loadMyAsyncData().then(
      externalData => {
        this._asyncRequest = null;
        this.setState({externalData});
      }
    );
  }

  componentWillUnmount() {
    if (this._asyncRequest) {
      this._asyncRequest.cancel();
    }
  }

  render() {
    if (this.state.externalData === null) {
      // Render loading state ...
    } else {
      // Render real UI ...
    }
  }
}

官方推薦使用componentDidMount處理組件異步請求的理由如下:

  1. 我們知道服務端渲染(SSR)的時候,不會執行componentDidMount但是會執行componentWillMount,此時如果數據請求卸載componentWillMount時,服務器和客戶端將會執行兩次請求,使用componentDidMount API將會減少不必要的請求。
  2. 在服務段渲染的時候,使用componentWillMount時可能有服務端內存泄漏(出現不調用componentWillmount)以及渲染中斷,導致異步渲染(渲染中斷導致componentWillMount不調用)的情況。
  3. react16.3開始 componentWillMount API被視爲不安全,逐漸棄用。

同時官方也推薦在componentDidMount進行事件訂閱的操作。有一點要注意的是如果你在componentDidMount使用了訂閱事件,那麼你要在卸載API componentWillUnmount中取消訂閱。請求發送數據同理。

下面是一段實例代碼,幫助你理解。

// After
class ExampleComponent extends React.Component {
  state = {
    subscribedValue: this.props.dataSource.value,
  };

  componentDidMount() {
    // Event listeners are only safe to add after mount,
    // So they won't leak if mount is interrupted or errors.
    this.props.dataSource.subscribe(
      this.handleSubscriptionChange
    );

    // External values could change between render and mount,
    // In some cases it may be important to handle this case.
    if (
      this.state.subscribedValue !==
      this.props.dataSource.value
    ) {
      this.setState({
        subscribedValue: this.props.dataSource.value,
      });
    }
  }

  componentWillUnmount() {
    this.props.dataSource.unsubscribe(
      this.handleSubscriptionChange
    );
  }

  handleSubscriptionChange = dataSource => {
    this.setState({
      subscribedValue: dataSource.value,
    });
  };
}

componentDidMount 函數運行完畢之後,我們的組件就顯示在屏幕上啦。

接下來介紹組件運行在瀏覽器之後,如何更新。

2.2 Update

我們的組件運行在瀏覽器的時候,會隨着數據狀態的變動進行更新。變動主要包括組件自身state的變動,以及父組件傳遞下來的props的變動。對應的流程圖部分如下:

在這裏插入圖片描述
可以看到執行過程類似Mount,都有 will render did 過程對應的API,但區別在於掛載中的組件需要根據propsstate的變化判定是否需要更新(當然通常情況下是需要更新)。

上述流程主要包括了以下方法:

  1. componentWillReceivePropsprops 觸發更新API;(棄用)
  2. shouldComponentUpdate: 確定是否觸發更新;(不常用)
  3. componentWillUpdate:渲染前的組件更新API;(棄用)
  4. render:渲染函數;(必用)
  5. componentDidUpdate:渲染後更新的API;(常用)

同時我們還會介紹 getDerivedStateFromProps 這一函數,它在掛載和更新都可以使用,但不是常用函數。

2.2.1 componentWillReceiveProps

這個函數會在已掛載的組件接受新的props之前被調用,如果你需要更新狀態以及相應prop的更改,
那麼你需要比較this.propsnextProps並在這個函數中使用this.setState()執行state轉換。但在Mount階段不會使用。

需要明白的是,只要父組件重新渲染,那麼即便props沒有更改,本方法也會調用。
雖然本方法是處於棄用(官方標記爲 unsafe)的狀態,但是也有一個重要的好處,就是可以定義子組件接受父組件props之後的狀態和行爲。
下面給出一個簡單例子:

//這種方式十分適合父子組件的互動,通常是父組件需要通過某些狀態控制子組件渲染亦或銷燬...
// credit to https://juejin.im/post/5a39de3d6fb9a045154405ec
componentWillReceiveProps(nextProps) {
//componentWillReceiveProps方法中第一個參數代表即將傳入的新的Props
    if (this.props.sharecard_show !== nextProps.sharecard_show){
        //在這裏我們仍可以通過this.props來獲取舊的外部狀態
        //通過新舊狀態的對比,來決定是否進行其他方法
        if (nextProps.sharecard_show){
            this.handleGetCard();
        }
    }
}

父組件通過setState的方法觸發更新渲染(可能不會改變子組件的props),從而觸發上述的函數。

2.2.2 getDerivedStateFromProps

static getDerivedStateFromProps(props, state)

這個函數是新版本的react中提出來的,會在調用render方法之前調用,並且在初始掛載和後續更新過程中都會被調用,它應該返回一個對象來更新state,如果返回null,那麼就不想需要更新內容。
不過這個函數也處於不常用的狀態,原因是會帶來代碼的冗餘。官方給出了一些替代的方案。

派生狀態會導致代碼冗餘,並使組件難以維護。 確保你已熟悉這些簡單的替代方案:

  1. 如果你需要執行副作用(例如,數據提取或動畫)以響應 props 中的更改,請改用componentDidUpdate。
  2. 如果只想在 prop 更改時重新計算某些數據,請使用 memoization helper 代替。
  3. 如果你想在 prop 更改時“重置”某些 state,請考慮使組件完全受控或使用 key 使組件完全不受控 代替。

這個方法每次渲染前都會觸發,不同於 componentWillReceiveProps僅在父組件重新渲染時進行觸發。圖一指出了更新階段的三種情況下,這個函數會被觸發:

  1. setState()方法
  2. props 改變
  3. forceUpdate方法調用

給出一個官方實例:

// credit to https://react.docschina.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#when-to-use-derived-state
class EmailInput extends Component {
  state = {
    email: this.props.defaultEmail,
    prevPropsUserID: this.props.userID
  };

  static getDerivedStateFromProps(props, state) {
    // Any time the current user changes,
    // Reset any parts of state that are tied to that user.
    // In this simple example, that's just the email.
    if (props.userID !== state.prevPropsUserID) {
      return {
        prevPropsUserID: props.userID,
        email: props.defaultEmail
      };
    }
    return null;
  }

  // ...
}

2.2.3 shouldComponentUpdate

這個函數發生在上面的函數 componentWillReceiveProps之後,其返回值true or false用於判斷當前的stateprops變化是否需要觸發組件的更新。

默認行爲是 state 每次發生變化組件都會重新渲染。大部分情況下,你應該遵循默認行爲。
這個函數是一個不常用的函數,如果你想避免一些無謂的渲染以提升性能的話,那麼可以考慮使用它。

用法比較簡單,一般是在函數內部添加一些比較條件即可。
給出一個簡答的例子幫助理解。

// credit to https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/update/using_should_component_update.html

/**
 * Performs equality by iterating through keys on an object and returning false
 * when any key has values which are not strictly equal between the arguments.
 * Returns true when the values of all keys are strictly equal.
 */
function shallowEqual(objA: mixed, objB: mixed): boolean {
  if (objA === objB) {
    return true;
  }

  if (typeof objA !== 'object' || objA === null ||
      typeof objB !== 'object' || objB === null) {
    return false;
  }

  var keysA = Object.keys(objA);
  var keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  // Test for A's keys different from B.
  var bHasOwnProperty = hasOwnProperty.bind(objB);
  for (var i = 0; i < keysA.length; i++) {
    if (!bHasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
      return false;
    }
  }

  return true;
}

function shallowCompare(instance, nextProps, nextState) {
  return (
    !shallowEqual(instance.props, nextProps) ||
    !shallowEqual(instance.state, nextState)
  );
}

var ReactComponentWithPureRenderMixin = {
  shouldComponentUpdate: function(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  },
};

2.2.4 componentWillUpdate

當組件收到新的propsstate,經過函數shouldComponentUpdate確認允許組件更新之後,這個函數會在組件更新渲染之前被調用。

這個函數在更新渲染前被使用,初始掛載階段的渲染將不會調用此方法。這個方法中不能調用setState方法,而且也不能執行任何操作觸發對 react組件的更新。

不過這個方法已經被新版本的react標記爲不安全,屬於棄用狀態。

不過還是提供一個簡單的例子,如下:

// credit to https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/update/tapping_into_componentwillupdate.html
// dispatching an action based on state change
componentWillUpdate(nextProps, nextState) {
  if (nextState.open == true && this.state.open == false) {
    this.props.onWillOpen();
  }
}

2.2.6 getSnapshotBeforeUpdate

這個函數比較不常用,在最近一次渲染輸出(提交到 DOM 節點)之前被調用(render之後)。它使得組件能在發生更改之前從 DOM 中捕獲一些信息(例如,滾動位置)。此生命週期的任何返回值將作爲參數傳遞給 componentDidUpdate()
從圖一中可看出,這個函數發生在render之後,屬於一個特殊的pre-commit階段,可以讀取DOM數據。

貼一個官網的簡單實例,關於如何捕獲滾動位置並利用:

class ScrollingList extends React.Component {
  constructor(props) {
    super(props);
    this.listRef = React.createRef();
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // 我們是否在 list 中添加新的 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>
    );
  }
}

2.2.5 componentDidUpdate

這個函數是最常用的了,會在更新並且shouldComponentUpdate返回true的情況下,render之後調用,但是mount階段的render則不會執行此方法。
組件進行更新之後,我們可以在這個函數中對DOM進行操作,以及setState()操作(需要注意包裹在條件語句中,不然一直處於setState更新狀態導致死循環),同時可以根據前後的props差別來進行網絡請求,這一點類似於componentDidMount

再提醒一遍,函數內部需要有條件約束才能進行DOM操作,setState和獲取數據,不然會導致一直更新死循環!

給出官方一個實例:

componentDidUpdate(prevProps) {
  // 典型用法(不要忘記比較 props):
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);  
  }
}

2.3 unmount

這個階段比較好理解,就是組件從DOM樹上銷燬卸載的過程,只涉及一個 componentWillUnmount API。

2.3.1 componentWillUnmount

我們通常會在此方法中執行必要的清理操作,如取消網絡請求,移除事件訂閱等,而且要不應該調用setState()方法。
這個階段我們就只負責清理就好了!一般和componentDidMountcomponentDidUpdate搭配一起出現。
摘取上面一個例子。

componentDidMount() {
    this.props.dataSource.subscribe(
      this.handleSubscriptionChange
    );
    if (
      this.state.subscribedValue !==
      this.props.dataSource.value
    ) {
      this.setState({
        subscribedValue: this.props.dataSource.value,
      });
    }
  }

  componentWillUnmount() {
    this.props.dataSource.unsubscribe(
      this.handleSubscriptionChange
    );
  }

到此,傳統但重要的生命週期API已經基本介紹完畢啦。


三、 使用 useEffect 方法替代生命週期API

useEffectreact 新版本推出的一個特別常用的 hooks 功能之一,useEffect 可以在組件渲染後實現各種不同的副作用,它使得函數式組件同樣具備編寫類似類組件生命週期函數的功能。在這裏我們僅僅介紹三個常用的生命週期替代方案,分別是:

  1. componentDidMount vs useEffect
  2. componentDidUpdate vs useEffect
  3. componentWillUnmount vs useEffect

useEffect作用於渲染後!!所以只能替代這三個render階段後的 lifecycle API~

詳細的useEffect使用請看官方文檔: 使用useEffect hook,文檔中給出了比較詳細的useEffect的API解釋,這裏我就不贅述了。理解完上面的概念之後,看下面幾個經過簡化後的例子之後,我們就可以快速進行遷移使用useEffect來替代原來的API啦。

3.1 componentDidMount vs useEffect

類組件中,我們這樣編寫componentDidMount

class Example extends React.Component {
  componentDidMount() {
    console.log('Did mount!');
  }
  render() {
    return null;
  }
}

在函數組件中,我們可以使用useEffect這樣編寫:

function Example() {
  // 注意不要省略第二個參數 [],這個參數保證函數只在掛載的時候進行,而不會在更新的時候執行。
  useEffect(() => console.log('mounted'), []);  
  return null;
}

3.2 componentDidUpdate vs useEffect

類組件中,我們這樣編寫componentDidUpdate

componentDidMount() {
  console.log('mounted or updated');
}

componentDidUpdate() {
  console.log('mounted or updated');
}

而在函數組件中,我們使用useEffect起到同樣的效果:

useEffect(() => console.log('mounted or updated'));  // 不需要指定第二個參數

值得一提的是,現在官方推薦的編程規範就是不區分 update 階段和 mount 階段,兩個階段視爲一致。

3.3 componentWillUnmount vs useEffect

類組件中,我們這樣編寫componentWillUnmount

componentWillUnmount() {
  console.log('will unmount');  
}

而在函數組件中,我們使用useEffect起到同樣的效果:

useEffect(() => {
  return () => {
    console.log('will unmount');  // 直接使用return返回一個函數,這個函數在unmount時執行。
  }
}, []);

你也可以使用useEffect 組合componentDidMountcomponentDidUnmount

useEffect(()=>{
	console.log("mounted")return () => {
		console.log("unmounted");
	}
}, [Started])  // 前後兩次執行的Started相等時,useEffect代碼生效,否則跳過。

這裏普及useEffect的兩點小tricks
1.就功能而言,使用多個useEffect實現代碼關注點分離。我們在一個函數組件內部可以不用將所有功能不一致的代碼都塞在一個 componentDidMount裏頭,我們就功能而言多次在一個組件內部使用useEffect,這樣會使得代碼更加的簡潔耐看。
2.使用條件跳過不必要的useEffect執行,實現性能優化。由於useEffect在每次mount或者update的時候都會執行,我們可以使用一些條件參數來跳過執行。就上面最後一個例子,我們可以傳入第二個參數,判斷前後參數是否一致,若一致則執行,否則就跳過。

至於其他比較不常用的生命週期函數,現在useEffect可能還做不到那種細粒度的操作(尤其是限制發生在render之後),但是相信官方一定會不斷完善這方面的支持滴!敬請期待!


四、 React Hooks 推薦閱讀

  1. 精讀 React 16 新特性;
  2. 精讀 React Hooks;
  3. Making sense of react hooks
  4. 對 React Hooks 的一些思考

參考自以下鏈接,並鳴謝:

  1. 深入React組件生命週期
  2. React 中文文檔 Hooks useEffect 部分
  3. The Lifecycle of a React Component
  4. React.Component 官方文檔
  5. react組件何時請求異步數據——componentWillMount或者componentDidMount?
  6. react:不常用的聲明週期方法
  7. react: 使用useEffect

如果本文有哪些地方出現了紕漏錯誤,還請大家大方指出,幫助本文完善得更好,謝謝!
感謝你的閱讀!

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