- 原文地址:React 16 Lifecycle Methods: How and When to Use Them
- 原文做者:Scott Domes
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:MarchYuanx
- 校對者:hanxiaosss, Zelda256
自從我關於這個主題的第一篇文章以來,React 組件 API 發生了顯著的變化。一些生命週期函數已被棄用,一些新的被引入。因此是時候進行更新了!html
(看看我是如何抵制開 shouldArticleUpdate
的玩笑的?這些就是約束。)前端
因爲此次生命週期 API 有點複雜,我將這些函數分爲四個部分:掛載、更新、卸載和錯誤。react
若是你對 React 還不太熟悉,個人文章在這提供了一個全面的介紹。android
提示:ios
使用可複用的組件庫更快地構建 React 應用程序。使用 Bit 共享你的組件,並使用它們來構建新的應用程序。試試看。git
組件發現與協做 · Bitgithub
咱們這個教程的示例應用程序很簡單:一個由塊組成的網格,每一個塊都有一個隨機尺寸,排列成一個磚石佈局(就像 Pinterest)。canvas
每隔幾秒鐘,頁面底部會加載一堆新的須要進行排列的塊。後端
這不是一個複雜的例子,但這裏有一個問題:咱們將使用 bricks.js 庫來對齊網格。
Brick.js 是一個很棒的工具,但它沒有對與 React 集成進行優化。它更適合普通的 JavaScript 或 jQuery。
那咱們爲何要用它?嗯,這是生命週期函數的一個常見用例:將非 React 工具集成到 React 範例中。
有時,實現功能最好的庫不是最適配的庫,生命週期函數有助於彌合這一差距。
如上所述,生命週期函數是最後的手段。
它們將被用於特殊狀況,當處於其餘回退時將沒法工做,如從新排列組件或改變 state。
生命週期方法(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
。
你還可使用構造函數進行函數綁定,這也是可選的。有關更多信息,請參閱此處:
constructor 的最多見用例: 設置 state、建立引用和函數綁定。
掛載時,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 對象。
渲染完成全部工做。它返回實際組件的 JSX。使用 React 時,大部分時間都將花費在這。
render 的最多見用例: 返回組件 JSX。
在咱們第一次渲染咱們的組件以後,此方法被調用。
若是你須要加載數據,請在此處執行操做。不要嘗試在 constructor、render 或是其餘瘋狂的地方加載數據。我會讓 Tyler McGinnis 解釋緣由:
你沒法確保在組件掛載以前 AJAX 請求不會被解析。若是確實如此,那就意味着你要在未掛載的組件上嘗試執行 setState,這不只不起做用,並且 React 會給你報一大堆錯誤。在 componentDidMount 中執行 AJAX 請求將保證這的確有一個組件須要更新。
你能夠在他的回答裏閱讀更多。
componentDidMount
也是你能夠作不少有趣事情的地方,那些在組件未加載時可無法作。如下是一些例子:
基本上,在這裏你能夠作全部沒有 DOM 你不能作的設置,並獲取全部你須要的數據。
如下咱們的示例:
componentDidMount() {
this.bricks = initializeGrid(this.grid.current);
layoutInitialGrid(this.bricks)
this.interval = setInterval(() => {
this.addBlocks();
}, 2000);
}
複製代碼
咱們使用 bricks.js
庫(在 initializeGrid
函數中調用)建立並排列網格。
而後咱們設置一個定時器,每兩秒添加更多的塊,模擬數據的加載。你能夠設想這是一個 loadRecommendations
的調用或是現實世界中的某些東西。
componentDidMount 的最多見用例: 發送 AJAX 請求以加載組件的數據。
是的,又是這個。如今,它更有用了。
若是你須要根據 prop 的改變動新 state,能夠經過返回新 state 對象來執行此操做。
**一樣,不推薦基於 props 更改 state。**這應該被視爲最後的手段。問問本身 —— 我須要存儲 state 嗎?我不能夠只從 props 自己得到正確的功能嗎?
也就是說,存在一些邊緣案例。如下是一些例子:
即便有上述狀況,一般也有更好的方法。可是,當最壞的狀況發生時,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。
當咱們有新的 props 時。典型的 React 法則是,當一個組件接收到新的 props 或者新的 state 時,它應該更新。
但咱們的組件有點擔心,得先徵得許可。
這是咱們獲得的 —— shouldComponentUpdate
函數,調用時以 nextProps
做爲第一個參數,nextState
是第二個。
shouldComponentUpdate
應該老是返回一個布爾值 —— 問題的答案,「我應該從新渲染嗎?」是的,小組件,你應該。它返回的默認值始終是 true。
但若是你擔憂浪費渲染資源和其它無心義的事 —— shouldComponentUpdate
是一個提升性能的好地方。
我寫了一篇關於用這種方式使用 shouldComponentUpdate
的文章 —— 請看:
在文章中,咱們談論有一個包含許多部分的表格。問題是當表從新渲染時,每一個部分也都會從新渲染,從而減慢了速度。
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
與更新組件被實際傳播到 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
。
如今,咱們的更改已經提交給 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 的已提交更改。
它快要結束了。
你的組件將會消失。也許是永遠。這很使人悲傷。
在此以前,它會詢問你是否有任何最後一刻前的請求。
你能夠在此處取消任何向外的網絡請求,或刪除與該組件關聯的全部事件監聽器。
總的來講,清除所涉及的組件的每一件事 —— 當它消失時,它應該徹底消失。
在咱們的例子中,咱們有一個在 componentDidMount
中調用的 setInterval
須要清理。
componentWillUnmount() {
clearInterval(this.interval);
}
複製代碼
componentWillUnmount 的最多見用例: 清理組件中全部的剩餘碎片。
有些東西出問題咯。
不是在你的組件自己,而是在它的某個子孫組件。
咱們想要將錯誤顯示在屏幕上。最簡單的方法是使用一個像 this.state.hasError
這樣的值,此時該值將轉換爲 true
。
static getDerivedStateFromError(error) {
return { hasError: true };
}
複製代碼
請注意,你必須返回更新的 state 對象。不要將此方法做用於任何其餘操做。而是使用下面的 componentDidCatch
。
getDerivedStateFromError的最多見用例: 更新 state 以顯示錯誤在屏幕上。
與上面很是類似,由於它在子組件中發生錯誤時被觸發。
區別在於不是爲了響應錯誤而更新 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 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。