翻譯自 React 16 Lifecycle Methods: How and When to Use Them,作者:Scott Domes
概述:最近在學習 react v16.8.4,發現元件的生命週期發生了一些變化,原有的一些方法被廢棄,所以看了官方API介紹,再結合上述文章,做個總結。
publish:2019-03-27
自從我寫了關於 React 元件生命週期的第一篇文章以來,相關的鉤子函式、API已經發生了重大變化。 一些生命週期方法已被棄用,並引入了一些新的方法。 所以是時候進行更新了!
由於這次生命週期 API 有點複雜,我將這些方法分為四個部分:安裝(Mounting),更新(Updating),解除安裝(Unmounting)和錯誤(Error)。
Mounting - 元件的掛載
├── constructor()
├── static getDerivedStateFromProps
├── render
constructor
如果您的元件是 Class Component,則呼叫的第一就是元件建構函式。 這不適用於 Functional Component。
相關建構函式可能是如下形式
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
}
}
複製程式碼
建構函式的引數為 props,你可以利用 super
來傳入。
在建構函式中,你可以初始化 state、設定預設值。甚至你可以依據 props 來建立 state。
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
counter: props.initialCounterValue,
};
}
}
複製程式碼
注意,現在建構函式是可選的,如果您的 Babel 設定支援 class fields,就可以像這樣初始化狀態:
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: [],
};
// 在 constructor 中建立 ref
this.grid = React.createRef();
}
}
複製程式碼
我們需要建構函式來呼叫 createRef,以建立對 HTMLElement 元素的引用。還可以使用建構函式進行函式繫結,這也是可選的
class Grid extends Component {
constructor(props) {
super(props);
this.state = {
blocks: [],
};
// 1
this.handleChange.bind(this)
}
// 2 等同於 1
handleChange = () => {}
}
複製程式碼
constructor總結: 建構函式的最常法:設定 state,建立 ref 和方法繫結。
getDerivedStateFromProps
掛載時,getDerivedStateFromProps 是渲染前呼叫的最後一個方法
一般使用 getDerivedStateFromProps 可以根據初始道具使用它來設定狀態。
static getDerivedStateFromProps(props, state) {
return { blocks: createBlocks(props.numberOfBlocks) };
}
// log {blocks: Array(20)}
console.log(this.state);
複製程式碼
上述程式碼中依據 props.numberOfBlocks 來初始化期望的 state(函式return為狀態)。
注意: 我們可以將此程式碼放在 constructor 中,與之相 getDerivedStateFromProps的優點是它更直觀 - 它僅用於設定狀態,而建構函式有多種用途。
總結: getDerivedStateFromProps 的最常見用例(在mount期間):根據初始props返回狀態物件。
render
完成所有渲染的工作。它返回實際元件的 JSX,使用React時,將花費大部分時間在這裡。
渲染的最常見用例:返回 JSX 元件。
componentDidMount
在第一次渲染元件之後,觸發此方法。
如果需要載入資料,請在此處執行。 不要嘗試在 constructor 中載入資料或渲染,原因,react-interview-questions.
由於AJAX是非同步的,所以無法保證在元件掛載之前 AJAX 請求完成解析。 如果確實如此,那就意味著你要在未掛載的元件上嘗試 setState,這不僅不起作用,而且 React 報錯。 在componentDidMount中執行 AJAX 將保證有一個要更新的元件。
componentDidMount 觸發時,元件已完成第一 render,所以可以進行一些操作:
- 在剛剛渲染的
<canvas>
元素上進行繪製 - 訪問 DOM 節點
- 新增事件偵聽器
基本上,在這裡你可以做所有依賴 DOM 的設定,並開始獲得你需要的所有資料,例如
componentDidMount() {
// 利用 ref 訪問 dom 元素
this.bricks = initializeGrid(this.grid.current);
this.interval = setInterval(() => {
// ajax 獲取資料
this.addBlocks();
}, 2000);
}
}
複製程式碼
總結: 呼叫 AJAX 以載入元件的資料。
Updating - 元件的更新
├── static getDerivedStateFromProps
├── render
getDerivedStateFromProps
是的,再來一次。 現在,它更有用了。
如果您需要根據 props 來更新 state,可以通過 return 新的狀態物件來完成該任務。
注,不建議依據 props 來處理 state,也就是說,只有逼不得已才使用該方法。 以下是一些例子:
- 當 video 、audio 的 source 改變時,需要重置元素;
- Server 資源更新後需要恢復 UI 元素;
- 當內容改變時關閉相關元素。
即使有上述情況,通常也有更好的方法。 但是 getDerivedStateFromProps 可以情況變得更壞。
總結: getDerivedStateFromProps 一般用於 props 不足以支撐業務時,利用它來更新 state。
shouldComponentUpdate
典型的 React 教條,當一個元件收到新的 State 或 Props時,它應該更新。
但我們的元件有點困惑,它不確定是否要進行更新。
shouldComponentUpdate 方法的第一個引數為 nextProps,第二個引數為 nextState。shouldComponentUpdate 返回一個布林值,用於控制元件是否更新。
shouldComponentUpdate 賦予我們一項能力,只有在你關心的 props 改變時元件會才更新。
總結: 可以準確地控制元件重新渲染的時間,常用於優化 React,具體。
render - 同上
getSnapshotBeforeUpdate
新新增的方法,觸發時刻在 render 之後,最新的渲染輸出提交給 DOM 之前。
呼叫渲染和最後顯示更改之間可能會有延遲, 如果你需要獲取 DOM 改變之前的一些資訊時,可以利用這個鉤子函式。
從渲染到提交這個過程是非同步的,所以如果在 componentWillUpdate 訪問 DOM 資訊,在 componentDidUpdate 使用時,該資訊可能發生了修改,這一部分以官網的例子來說明
class ScrollingList extends React.Component {
listRef = React.createRef();
getSnapshotBeforeUpdate(prevProps, prevState) {
// 如果在列表中新增新專案
// 獲取列表的當前高度,以便我們稍後調整滾動
if (prevProps.list.length < this.props.list.length) {
return this.listRef.current.scrollHeight;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 如果我們 snapshot 的值不為空,說明新增了新專案
// 調整滾動,以便這些新專案不會將舊專案推出可視區域
if (snapshot !== null) {
this.listRef.current.scrollTop +=
this.listRef.current.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
複製程式碼
總結: 檢視當前 DOM 的某些屬性,並將該值傳遞給componentDidUpdat。
componentDidUpdate
所有的改變都已經提交給 DOM。
componentDidUpdate 包含三個引數,之前的 props、state,以及 getSnapshotBeforeUpdate 的返回值,具體如上述例子。
總結: 對已經改變的 Dom 作出相關響應。
Unmounting
componentWillUnmount
元件快要結束了。
在元件登出之前,它會詢問您是否有任何最後一刻的請求。
您可以在此處取消任何傳出網路請求,或刪除與該元件關聯的所有事件偵聽器。
基本上,清理任何事情都只涉及有問題的元件 - 當它消失時,它應該完全消失。
總結: 清除事件監聽、定時器等,防止記憶體洩漏。
Errors
getDerivedStateFromError
發生了一些異常。
它能夠捕捉在他們的子元件樹中任意地方的 JavaScript 錯誤,記錄這些錯誤。當我們要展示一個 error 頁面時,可以利用它來完成。
static getDerivedStateFromError(error) {
return { hasError: true };
}
複製程式碼
注意: 您必須返回更新的狀態物件。 不要將此方法用於任何副作用。 而是使用下面的 componentDidCatch。
總結: 依據錯誤資訊來修改元件的 state,同時展示出 error 頁面。
componentDidCatch
與上面非常相似,因為它在子元件中發生錯誤時被觸發。
與 getDerivedStateFromError 區別在於不是為了響應錯誤而更新狀態,可以執行任何副作用,例如記錄錯誤。
componentDidCatch(error, info) {
sendErrorLog(error, info);
}
複製程式碼
注意: componentDidCatch 僅會捕獲渲染/生命週期方法中的錯誤。 如果在單擊處理程式中引發錯誤,則不會捕獲它。
通常只在特殊的地方使用錯誤邊界元件的 componentDidCatch。 這些元件包裝子樹的唯一目的是捕獲和記錄錯誤。
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;
}
}
複製程式碼
總結: 捕獲、列印出錯誤資訊。
總結
以上是React v16的生命週期鉤子所適用的場景和具體使用方法。