文章目錄
- 一、 引言
- 二、 組件生命週期過程及API詳解
- 2.1 Mount
- 2.1.1 constructor
- 2.1.2 getDefaultProps
- 2.1.3 getInitialState
- 2.1.4 componentWillMount
- 2.1.5 render
- 2.1.6 componentDidMount
- 2.2 Update
- 2.2.1 componentWillReceiveProps
- 2.2.2 getDerivedStateFromProps
- 2.2.3 shouldComponentUpdate
- 2.2.4 componentWillUpdate
- 2.2.6 getSnapshotBeforeUpdate
- 2.2.5 componentDidUpdate
- 2.3 unmount
- 三、 使用 useEffect 方法替代生命週期API
- 3.1 componentDidMount vs useEffect
- 3.2 componentDidUpdate vs useEffect
- 3.3 componentWillUnmount vs useEffect
- 四、 React Hooks 推薦閱讀
本文篇幅較長,如果你想要:
- 完整了解整個組件生命週期的變更和對應函數API,那麼請從頭開始;
- 快速理解傳統生命週期函數API的含義和使用,那麼點擊目錄第二部分;
- 快速瞭解如何在函數組件中使用useEffect來替代原有的生命週期API,那麼點擊目錄第三部分。
一、 引言
如果你是一名前端開發者,並且使用React開發框架,那麼你一定繞不開React的一個核心概念——組件生命週期。 組件的生命週期描述的是一個組件從創建,渲染,加載,顯示到卸載的整個過程。其大致過程如下:
圖片來源請戳此鏈接: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
函數。
綜上,本文主要關注以下內容:
- 類組件生命週期理解和API用法;
- 如何在函數組件中使用
useEffect
來替代類組件的生命週期API。
攥寫本文的初衷是,本人最近在經常使用React
框架開發,對生命週期這一核心概念有必要進行一番梳理,同時也發現網上關於講解如何使用新特性Hooks
來定製生命週期行爲的博文數目較少而且不全面,所以我花費幾個小時的時間來整理這些概念,總結方法並提供具體的代碼實例,希望能幫助到大家,互相學習。
二、 組件生命週期過程及API詳解
這裏先放我從谷歌搜到的一張大家都在用的生命週期圖(圖源見鏈接),直接從API入手描述整個組件的生命週期過程,如果你已經使用過相關的API,那相當明瞭,但考慮到初學者,我還是基於個人理解給大家歸納總結一下,幫助大家理解、記憶和使用。
本文將API歸爲三類:
Mount
:掛載APIUpdate
: 更新APIUnmount
:卸載API
結合上述流程圖,我們從這三類API開始展開敘述,簡單講解一些不常用的API (不完全覆蓋),多把文字放在常用的API(基本覆蓋)上。
2.1 Mount
掛載一個組件的過程是先實例化創建該組件並渲染,然後讀取DOM使得組件運行在瀏覽器中。整個過程涉及到的 API 函數如下:
當組件在客戶端實例化首次創建時,以下方法依次被調用:
getDefaultProps
(ES5語法,過時)getInitialState
(ES5語法,過時)componentWillMount
(棄用)render
(必須使用)componentDidMount
(常用)
當組件屬於服務端渲染時,以下方法依次被調用:
getDefaultProps
getInitialState
componentWillMount
render
服務端渲染沒有 componentDidMount
方法時因爲其執行階段是組件掛載(插入DOM
樹)之後,纔會執行,發生在客戶端。
但需要提醒的是,getDefaultProps
和 getInitialState
方法僅適用於非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
難以定義和識別;
通常構造函數的作用就是:
- 初始化內部
state
變量 - 爲事件處理函數綁定實例
需要注意的時,在構造函數裏不要出現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
賦初始值,以起到同樣的效果。
getInitialState
和getDefaultProps
的區別在於,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
,用來表示組件的結構。需要注意幾點的就是:
- 只能通過
props
和state
來訪問數據,不能修改; - 支持返回
null
,false
或其他react
組件; - 只能返回一個頂級組件,如果出現多個組件,你需要用
<div>
標籤變成一個組件; - 無法改變組件的狀態,
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
處理組件異步請求的理由如下:
- 我們知道服務端渲染
(SSR)
的時候,不會執行componentDidMount
但是會執行componentWillMount
,此時如果數據請求卸載componentWillMount
時,服務器和客戶端將會執行兩次請求,使用componentDidMount
API將會減少不必要的請求。 - 在服務段渲染的時候,使用
componentWillMount
時可能有服務端內存泄漏(出現不調用componentWillmount
)以及渲染中斷,導致異步渲染(渲染中斷導致componentWillMount
不調用)的情況。 - 從
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,但區別在於掛載中的組件需要根據props
和state
的變化判定是否需要更新(當然通常情況下是需要更新)。
上述流程主要包括了以下方法:
componentWillReceiveProps
:props
觸發更新API;(棄用)shouldComponentUpdate
: 確定是否觸發更新;(不常用)componentWillUpdate
:渲染前的組件更新API;(棄用)render
:渲染函數;(必用)componentDidUpdate
:渲染後更新的API;(常用)
同時我們還會介紹 getDerivedStateFromProps
這一函數,它在掛載和更新都可以使用,但不是常用函數。
2.2.1 componentWillReceiveProps
這個函數會在已掛載的組件接受新的props
之前被調用,如果你需要更新狀態以及相應prop
的更改,
那麼你需要比較this.props
和nextProps
並在這個函數中使用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
,那麼就不想需要更新內容。
不過這個函數也處於不常用的狀態,原因是會帶來代碼的冗餘。官方給出了一些替代的方案。
派生狀態會導致代碼冗餘,並使組件難以維護。 確保你已熟悉這些簡單的替代方案:
- 如果你需要執行副作用(例如,數據提取或動畫)以響應 props 中的更改,請改用componentDidUpdate。
- 如果只想在 prop 更改時重新計算某些數據,請使用 memoization helper 代替。
- 如果你想在 prop 更改時“重置”某些 state,請考慮使組件完全受控或使用 key 使組件完全不受控 代替。
這個方法每次渲染前都會觸發,不同於 componentWillReceiveProps
僅在父組件重新渲染時進行觸發。圖一指出了更新階段的三種情況下,這個函數會被觸發:
setState()
方法props
改變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
用於判斷當前的state
和props
變化是否需要觸發組件的更新。
默認行爲是 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
當組件收到新的props
或state
,經過函數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()
方法。
這個階段我們就只負責清理就好了!一般和componentDidMount
和componentDidUpdate
搭配一起出現。
摘取上面一個例子。
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
useEffect
是react
新版本推出的一個特別常用的 hooks
功能之一,useEffect
可以在組件渲染後實現各種不同的副作用,它使得函數式組件同樣具備編寫類似類組件生命週期函數的功能。在這裏我們僅僅介紹三個常用的生命週期替代方案,分別是:
- componentDidMount vs useEffect
- componentDidUpdate vs useEffect
- 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
組合componentDidMount
和 componentDidUnmount
。
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 推薦閱讀
參考自以下鏈接,並鳴謝:
如果本文有哪些地方出現了紕漏錯誤,還請大家大方指出,幫助本文完善得更好,謝謝!
感謝你的閱讀!