[譯] React 16 生命週期函數:如何以及什麼時候使用它們

React 16 生命週期函數:如何以及什麼時候使用它們

React 組件生命週期的修訂版最新指南

自從我關於這個主題的第一篇文章以來,React 組件 API 發生了顯著的變化。一些生命週期函數已被棄用,一些新的被引入。因此是時候進行更新了!html

(看看我是如何抵制開 shouldArticleUpdate 的玩笑的?這些就是約束。)前端

因爲此次生命週期 API 有點複雜,我將這些函數分爲四個部分:掛載、更新、卸載和錯誤。react

若是你對 React 還不太熟悉,個人文章在這提供了一個全面的介紹。android

提示:ios

使用可複用的組件庫更快地構建 React 應用程序。使用 Bit 共享你的組件,並使用它們來構建新的應用程序。試試看。git

發現、嘗試、使用集成 bit 的 React 組件

組件發現與協做 · Bitgithub

問題

咱們這個教程的示例應用程序很簡單:一個由塊組成的網格,每一個塊都有一個隨機尺寸,排列成一個磚石佈局(就像 Pinterest)。canvas

每隔幾秒鐘,頁面底部會加載一堆新的須要進行排列的塊。後端

你能夠查看最終的應用程序及其代碼數組

這不是一個複雜的例子,但這裏有一個問題:咱們將使用 bricks.js 庫來對齊網格。

Brick.js 是一個很棒的工具,但它沒有對與 React 集成進行優化。它更適合普通的 JavaScript 或 jQuery。

那咱們爲何要用它?嗯,這是生命週期函數的一個常見用例:將非 React 工具集成到 React 範例中

有時,實現功能最好的庫不是最適配的庫,生命週期函數有助於彌合這一差距。


在咱們深刻研究前的最後一點

如上所述,生命週期函數是最後的手段。

它們將被用於特殊狀況,當處於其餘回退時將沒法工做,如從新排列組件或改變 state。

生命週期方法(constructor 除外)很難解釋。它們會增長應用程序的複雜性。除非必須,不然不要使用它們。

不廢話了,讓咱們來看看吧。

掛載

constructor

若是你的組件是類組件,第一個被調用的是組件構造函數。這不適用於函數組件。

你的構造函數可能以下所示:

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0,
    };
  }
}
複製代碼

調用構造函數時傳入組件的 props。** 你必須調用 super 並傳入 props。**

而後,你能夠初始化 state,設置默認值。你甚至能夠根據 props 設置 state:

class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: props.initialCounterValue,
    };
  }
}
複製代碼

請注意,使用構造函數的方式是可選的,若是你的 Babel 設置支持類字段,則能夠這樣初始化 state:

class MyComponent extends Component {
  state = {
    counter: 0,
  };
}
複製代碼

這種方法廣受歡迎。 你仍然能夠根據 props 設置 state:

class MyComponent extends Component {
  state = {
    counter: this.props.initialCounterValue,
  };
}
複製代碼

可是,若是須要使用 ref,你可能仍須要構造函數。如下是咱們的網格應用程序中的示例:

class Grid extends Component {
  constructor(props) {
    super(props);
    this.state = {
      blocks: [],
    };
    this.grid = React.createRef();
  }
複製代碼

咱們須要構造函數調用 createRef 來建立對 grid 元素的引用,所以咱們能夠將它傳遞給 bricks.js

你還可使用構造函數進行函數綁定,這也是可選的。有關更多信息,請參閱此處

React 綁定模式:處理 this 的 5 種方法

constructor 的最多見用例: 設置 state、建立引用和函數綁定。

getDerivedStateFromProps

掛載時,getDerivedStateFromProps 是渲染前調用的最後一個方法。你能夠經過它根據初始的 props 設置 state。這是示例 Grid 組件的代碼:

static getDerivedStateFromProps(props, state) {
  return { blocks: createBlocks(props.numberOfBlocks) };
}
複製代碼

咱們來看 numberOfBlocks prop,使用它來建立一組隨機大小的塊。而後咱們返回想要的 state 對象。

如下是調用 console.log(this.state) 以後的內容:

console.log(this.state);
// -> {blocks: Array(20)}
複製代碼

請注意,咱們能夠將此代碼放在 constructor 中。getDerivedStateFromProps 的優勢是它更直觀 —— 它用於設置 state,而構造函數有多種用途。getDerivedStateFromProps 在掛載和更新以前都會被調用,咱們稍後會看到。

**getDerivedStateFromProps 的最多見用例(掛載期間):**返回基於初始 props 的 state 對象。

render

渲染完成全部工做。它返回實際組件的 JSX。使用 React 時,大部分時間都將花費在這。

render 的最多見用例: 返回組件 JSX。

componentDidMount

在咱們第一次渲染咱們的組件以後,此方法被調用。

若是你須要加載數據,請在此處執行操做。不要嘗試在 constructor、render 或是其餘瘋狂的地方加載數據。我會讓 Tyler McGinnis 解釋緣由:

你沒法確保在組件掛載以前 AJAX 請求不會被解析。若是確實如此,那就意味着你要在未掛載的組件上嘗試執行 setState,這不只不起做用,並且 React 會給你報一大堆錯誤。在 componentDidMount 中執行 AJAX 請求將保證這的確有一個組件須要更新。

你能夠在他的回答裏閱讀更多。

componentDidMount 也是你能夠作不少有趣事情的地方,那些在組件未加載時可無法作。如下是一些例子:

  • 繪製剛剛渲染的 <canvas> 元素
  • 在元素集合中初始化磚石網格佈局(就是咱們正在作的!)
  • 添加事件監聽器

基本上,在這裏你能夠作全部沒有 DOM 你不能作的設置,並獲取全部你須要的數據。

如下咱們的示例:

componentDidMount() {
    this.bricks = initializeGrid(this.grid.current);
    layoutInitialGrid(this.bricks)

    this.interval = setInterval(() => {
      this.addBlocks();
    }, 2000);
  }
複製代碼

咱們使用 bricks.js 庫(在 initializeGrid 函數中調用)建立並排列網格。

而後咱們設置一個定時器,每兩秒添加更多的塊,模擬數據的加載。你能夠設想這是一個 loadRecommendations 的調用或是現實世界中的某些東西。

componentDidMount 的最多見用例: 發送 AJAX 請求以加載組件的數據。

更新

getDerivedStateFromProps

是的,又是這個。如今,它更有用了。

若是你須要根據 prop 的改變動新 state,能夠經過返回新 state 對象來執行此操做。

**一樣,不推薦基於 props 更改 state。**這應該被視爲最後的手段。問問本身 —— 我須要存儲 state 嗎?我不能夠只從 props 自己得到正確的功能嗎?

也就是說,存在一些邊緣案例。如下是一些例子:

  • 當源更改時重置視頻或音頻元素
  • 使用服務器的更新刷新 UI 元素
  • 當內容改變時關閉手風琴元素

即便有上述狀況,一般也有更好的方法。可是,當最壞的狀況發生時,getDerivedStateFromProps 會幫你挽救回來。

使用咱們的示例應用程序,假設咱們的 Grid 組件的 numberOfBlocks prop 增長了。可是咱們已經「加載」了比新數量更多的塊。使用相同的值沒有意義。因此咱們這樣作:

static getDerivedStateFromProps(props, state) {
  if (state.blocks.length > 0) {
    return {};
  }

  return { blocks: createBlocks(props.numberOfBlocks) };
}
複製代碼

若是咱們當前 state 中的塊數超過了新的 prop,咱們根本不更新狀態,返回一個空對象。

(關於 static 函數的最後一點,好比 getDerivedStateFromProps:你沒有經過 this 訪問組件的權限。例如,咱們沒法訪問的網格的引用。)

getDerivedStateFromProps 的最多見用例: 當 props 自己不足時,根據狀況更新 state。

shouldComponentUpdate

當咱們有新的 props 時。典型的 React 法則是,當一個組件接收到新的 props 或者新的 state 時,它應該更新。

但咱們的組件有點擔心,得先徵得許可。

這是咱們獲得的 —— shouldComponentUpdate 函數,調用時以 nextProps 做爲第一個參數,nextState 是第二個。

shouldComponentUpdate應該老是返回一個布爾值 —— 問題的答案,「我應該從新渲染嗎?」是的,小組件,你應該。它返回的默認值始終是 true。

但若是你擔憂浪費渲染資源和其它無心義的事 —— shouldComponentUpdate 是一個提升性能的好地方。

我寫了一篇關於用這種方式使用 shouldComponentUpdate 的文章 —— 請看:

如何對 React 組件進行基準測試:快速簡要指南

在文章中,咱們談論有一個包含許多部分的表格。問題是當表從新渲染時,每一個部分也都會從新渲染,從而減慢了速度。

shouldComponentUpdate 讓咱們可以說:組件只在你關心的 props 改變時才更新。

但請牢記,若是你設置了並忘記了它會致使重大問題,由於你的 React 組件將沒法正常更新。因此要謹慎使用。

在咱們的網格應用程序中,咱們以前已經肯定了有時咱們將忽略 this.props.numberOfBlocks 的新值。默認行爲表示咱們的組件仍然會從新渲染,由於它收到了新的 props。這太浪費了。

shouldComponentUpdate(nextProps, nextState) {
  // Only update if bricks change
  return nextState.blocks.length > this.state.blocks.length;
}
複製代碼

如今咱們能夠說:只有當 state 中的塊數改變時,組件才應該更新。

shouldComponentUpdate 的最多見用例: 精確控制組件的從新渲染。

render

和以前同樣!

getSnapshotBeforeUpdate

這個函數是一個有趣的新增功能。

請注意,它是在 render 與更新組件被實際傳播到 DOM 之間調用的。在你的組件中,它做爲最後一次看到以前的 props 和 state 的機會存在。

爲何?好吧,在調用 render 和顯示更改之間可能會有一段延遲。若是你須要在整合最新 render 調用的結果時知道 DOM 是什麼**,這裏就是你能夠找到答案的地方。

這是一個例子。假設咱們的團隊負責人決定,若是用戶在加載新塊時位於網格底部,則應將其向下滾動到屏幕的底部。

換句話說:當網格擴展時,若是它們位於底部,請讓它們繼續在底部。

getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevState.blocks.length < this.state.blocks.length) {
      const grid = this.grid.current;
      const isAtBottomOfGrid =
        window.innerHeight + window.pageYOffset === grid.scrollHeight;

      return { isAtBottomOfGrid };
    }

    return null;
  }
複製代碼

這就是說:若是用戶滾動到底部,則返回以下對象:{isAtBottomOfGrid:true}。若是沒有,則返回 null

你應該返回 null 或從 getSnapshotBeforeUpdate 獲取的值。

爲何?咱們立刻就能看到。

getSnapshotBeforeUpdate 的最多見用例: 查看當前 DOM 的一些屬性,並將值傳給 componentDidUpdate

componentDidUpdate

如今,咱們的更改已經提交給 DOM。

componentDidUpdate 中,咱們能夠訪問三個東西:以前的 props、以前的 state 以及咱們從 getSnapshotBeforeUpdate 返回的任何值。

完成上面的例子:

componentDidUpdate(prevProps, prevState, snapshot) {
  this.bricks.pack();

  if (snapshot.isAtBottomOfGrid) {
    window.scrollTo({
      top: this.grid.current.scrollHeight,
      behavior: 'smooth',
    });
  }
}
複製代碼

首先,咱們使用 Bricks.js 的 pack 函數從新佈局網格。

而後,若是咱們的快照顯示用戶原先就位於網格的底部,咱們將它們向下滾動到新塊的底部。

componentDidUpdate 的最多見用例: 響應(哈哈!)DOM 的已提交更改。

卸載

componentWillUnmount

它快要結束了。

你的組件將會消失。也許是永遠。這很使人悲傷。

在此以前,它會詢問你是否有任何最後一刻前的請求。

你能夠在此處取消任何向外的網絡請求,或刪除與該組件關聯的全部事件監聽器。

總的來講,清除所涉及的組件的每一件事 —— 當它消失時,它應該徹底消失。

在咱們的例子中,咱們有一個在 componentDidMount 中調用的 setInterval 須要清理。

componentWillUnmount() {
  clearInterval(this.interval);
}
複製代碼

componentWillUnmount 的最多見用例: 清理組件中全部的剩餘碎片。

錯誤

getDerivedStateFromError

有些東西出問題咯。

不是在你的組件自己,而是在它的某個子孫組件。

咱們想要將錯誤顯示在屏幕上。最簡單的方法是使用一個像 this.state.hasError 這樣的值,此時該值將轉換爲 true

static getDerivedStateFromError(error) {
  return { hasError: true };
}
複製代碼

請注意,你必須返回更新的 state 對象。不要將此方法做用於任何其餘操做。而是使用下面的 componentDidCatch

getDerivedStateFromError的最多見用例: 更新 state 以顯示錯誤在屏幕上。

componentDidCatch

與上面很是類似,由於它在子組件中發生錯誤時被觸發。

區別在於不是爲了響應錯誤而更新 state,咱們如今能夠執行任何其餘操做,例如記錄錯誤。

componentDidCatch(error, info) {
  sendErrorLog(error, info);
}
複製代碼

error 是實際的錯誤消息(未定義的變量之類),info 是堆棧跟蹤(In Component, in div, etc)。

請注意,componentDidCatch 僅適用於渲染/生命週期函數中的錯誤。若是你的應用程序在點擊事件中拋出錯誤,它不會被捕獲。

你一般只在特殊的錯誤邊界組件中使用 componentDidCatch。這些組件封裝子組件的惟一目的是捕獲並記錄錯誤。

例如,此錯誤邊界將捕獲錯誤並呈現「Oops!」消息而不是子組件:

class ErrorBoundary extends Component {
  state = { errorMessage: null };

  static getDerivedStateFromError(error) {
    return { errorMessage: error.message };
  }

  componentDidCatch(error, info) {
    console.log(error, info);
  }

  render() {
    if (this.state.errorMessage) {
      return <h1>Oops! {this.state.errorMessage}</h1>;
    }

    return this.props.children;
  }
}
複製代碼

componentDidCatch 的最多見用例: 捕獲並記錄錯誤。

結論

就是這樣!這些都是生命週期函數,任你使用。

你能夠查看示例程序的代碼最終產品

感謝閱讀!請隨意在下面發表評論並提出任何問題,歡迎交流!


延伸閱讀

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索