生命周期
現(xiàn)在流行的前端框架,無(wú)論是angular還是React赋兵,又或是Angular2以及以上庄拇,都由框架自身提供了生命周期(有的叫生命周期鉤子)供開(kāi)發(fā)者使用癞尚。
下面我們看下上面幾個(gè)框架的生命周期:
Vue生命周期:
Angular生命周期:
Hook | Purpose and Timing |
---|---|
ngOnChanges() |
Angular(重新)設(shè)置數(shù)據(jù)綁定輸入屬性時(shí)的響應(yīng)。該方法接收[SimpleChanges](https://angular.io/api/core/SimpleChanges) 當(dāng)前和先前屬性值的對(duì)象惋耙。ngOnInit() 在一個(gè)或多個(gè)數(shù)據(jù)綁定輸入屬性發(fā)生更改 之前和之后調(diào)用捣炬。 |
ngOnInit() |
在Angular首次顯示數(shù)據(jù)綁定屬性并設(shè)置指令/組件的輸入屬性后初始化指令/組件。在第一次之后 調(diào)用一次绽榛。 ngOnChanges() |
ngDoCheck() |
檢測(cè)Angular無(wú)法或不會(huì)自行檢測(cè)的更改并對(duì)其進(jìn)行操作湿酸。在每次更改檢測(cè)運(yùn)行期間,在ngOnChanges()和之后立即調(diào)用ngOnInit()灭美。 |
[ngAfterContentInit()] |
在Angular將外部?jī)?nèi)容投影到組件的視圖/指令所在的視圖后進(jìn)行響應(yīng)推溃。在第一次之后 調(diào)用一次ngDoCheck()。 |
ngAfterContentChecked() |
在Angular檢查投射到指令/組件中的內(nèi)容后響應(yīng)届腐。在[ngAfterContentInit()](https://angular.io/api/router/RouterLinkActive#ngAfterContentInit) 隨后和隨后的每一次調(diào)用之后ngDoCheck() 铁坎。 |
[ngAfterViewInit()] |
在Angular初始化組件的視圖和子視圖/指令所在的視圖后響應(yīng)。在第一次之后 調(diào)用一次ngAfterContentChecked()犁苏。 |
ngAfterViewChecked() |
在Angular檢查組件的視圖和子視圖/指令所在的視圖后響應(yīng)硬萍。在[ngAfterViewInit()] 隨后和隨后的每一次調(diào)用之后ngAfterContentChecked() 。 |
ngOnDestroy() |
就在Angular破壞指令/組件之前進(jìn)行清理围详。取消訂閱Observable并分離事件處理程序以避免內(nèi)存泄漏朴乖。在 Angular破壞指令/組件之前 調(diào)用。 |
React生命周期(16.0之前):
React生命周期(16.0后):
我們下面看一個(gè)例子助赞,React代碼中是如何使用生命周期的买羞。
class Demo extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
this.fetchList();
}
fetchList() {}
render() {
return <span>demo page</span>;
}
}
我們都知道,在react中雹食,有兩種類型的組件畜普,function組件和class組件。其中class類不僅允許內(nèi)部狀態(tài)(state
)的存在群叶,還有完整的生命周期鉤子吃挑。
前面說(shuō)到class類組件有完整的生命周期鉤子钝荡。這些生命周期鉤子是從哪來(lái)的呢?畢竟class類組件就是原生的class類寫法儒鹿。
其實(shí)React內(nèi)置了一個(gè)Component類,生命周期鉤子都是從它這里來(lái)的几晤,麻煩的地方就是每次都要繼承约炎。
綜合以上的對(duì)比,我們可以看出蟹瘾,生命周期的出現(xiàn)圾浅,主要是為了便于開(kāi)發(fā)&&更好的開(kāi)發(fā)。
React 生命周期使用小提示:
- getDerivedStateFromProps被React官方歸類為不常用的生命周期憾朴,能不用就盡量不用狸捕,前面用那么多篇幅講這個(gè)生命周期主要是為了加深對(duì)Reac運(yùn)行機(jī)制的理解。
- unsafe
下面開(kāi)始咱們今天的主題Hooks众雷。
Hooks
React v16.7.0-alpha 中第一次引入了 Hooks 的概念灸拍, 為什么要引入這個(gè)東西呢?
有兩個(gè)原因:
- React 官方覺(jué)得 class組件太難以理解砾省,OO(面向?qū)ο螅┨y懂了
- React 官方覺(jué)得 鸡岗, React 生命周期太難理解。
最終目的就是编兄, 開(kāi)發(fā)者不用去理解class轩性, 也不用操心生命周期方法。
但是React 官方又說(shuō)狠鸳, Hooks的目的并不是消滅類組件揣苏。
此處表示??
但無(wú)論如何,既然react官方這樣說(shuō)了件舵,那咱們就來(lái)了解一下這個(gè) Hooks卸察。
1. API
我們來(lái)看下Hooks的API,下面是官網(wǎng)上的截圖:
乍一看還是挺多的铅祸, 其實(shí)有很多的Hook 還處在實(shí)驗(yàn)階段蛾派,很可能有一部分要被砍掉, 目前大家只需要熟悉的个少, 三個(gè)就夠了:
- useState
- useEffect
- useContext
1.1 useState
看例子 - hooksdemo
進(jìn)去就調(diào)用了useState洪乍, 傳入 0,對(duì)state 進(jìn)行初始化夜焦,此時(shí)count 就是0壳澳, 返回一個(gè)數(shù)組, 第一個(gè)元素就是 state 的值茫经,第二個(gè)元素是更新 state 的函數(shù)巷波。
// 下面代碼等同于: const [count, setCount] = useState(0);
const result = useState(0);
const count = result[0];
const setCount = result[1];
利用 count 可以讀取到這個(gè) state萎津,利用 setCount 可以更新這個(gè) state,而且我們完全可以控制這兩個(gè)變量的命名抹镊,只要高興锉屈,你完全可以這么寫:
const [theCount, updateCount] = useState(0);
因?yàn)?useState 在 Counter 這個(gè)函數(shù)體中,每次 Counter 被渲染的時(shí)候垮耳,這個(gè) useState 調(diào)用都會(huì)被執(zhí)行颈渊,useState 自己肯定不是一個(gè)純函數(shù),因?yàn)樗獏^(qū)分第一次調(diào)用(組件被 mount 時(shí))和后續(xù)調(diào)用(重復(fù)渲染時(shí))终佛,只有第一次才用得上參數(shù)的初始值俊嗽,而后續(xù)的調(diào)用就返回“記住”的 state 值。
看到這里铃彰,心里可能會(huì)有這樣的疑問(wèn):如果組件中多次使用 useState 怎么辦绍豁?React 如何“記住”哪個(gè)狀態(tài)對(duì)應(yīng)哪個(gè)變量?
React 是完全根據(jù) useState 的調(diào)用順序來(lái)“記住”狀態(tài)歸屬的牙捉,假設(shè)組件代碼如下:
const Counter = () => {
const [count, setCount] = useState(0);
const [foo, updateFoo] = useState('foo');
// ...
}
每一次 Counter 被渲染竹揍,都是第一次 useState 調(diào)用獲得 count 和 setCount,第二次 useState 調(diào)用獲得 foo 和 updateFoo(這里我故意讓命名不用 set 前綴邪铲,可見(jiàn)函數(shù)名可以隨意)鬼佣。
React 是渲染過(guò)程中的“上帝”,每一次渲染 Counter 都要由 React 發(fā)起霜浴,所以它有機(jī)會(huì)準(zhǔn)備好一個(gè)內(nèi)存記錄晶衷,當(dāng)開(kāi)始執(zhí)行的時(shí)候,每一次 useState 調(diào)用對(duì)應(yīng)內(nèi)存記錄上一個(gè)位置阴孟,而且是按照順序來(lái)記錄的晌纫。React 不知道你把 useState 等 Hooks API 返回的結(jié)果賦值給什么變量,但是它也不需要知道永丝,它只需要按照 useState 調(diào)用順序記錄就好了锹漱。
你可以理解為會(huì)有一個(gè)槽去記錄狀態(tài)。
正因?yàn)檫@個(gè)原因慕嚷,Hooks哥牍,千萬(wàn)不要在 if 語(yǔ)句或者 for 循環(huán)語(yǔ)句中使用!
像下面的代碼喝检,肯定會(huì)出亂子的:
let showFruit = true;
let fruit, setFruit;
if (showFruit) {
[fruit, setFruit] = useState("banana");
showFruit = false;
}
因?yàn)闂l件判斷嗅辣,讓每次渲染中 useState 的調(diào)用次序不一致了,于是 React 就錯(cuò)亂了挠说。
1.2 useEffect
除了 useState澡谭,React 還提供 useEffect,用于支持組件中增加副作用的支持损俭。
在 React 組件生命周期中如果要做有副作用的操作蛙奖,代碼放在哪里潘酗?
如果您之前編寫過(guò)React類組件,則應(yīng)熟悉componentDidMount雁仲,componentDidUpdate和componentWillUnmount等生命周期方法仔夺。這副作用的代碼就放在這里。
useEffect Hook是這三種生命周期方法的組合攒砖。
useEffect當(dāng)組件第一次完成加載時(shí)運(yùn)行一次缸兔,然后每次更新組件狀態(tài)時(shí)運(yùn)行一次。因?yàn)榘粹o單擊正在修改狀態(tài)祭衩,即組件useEffect 方法運(yùn)行灶体。
在 Counter 組件阅签,如果我們想要在用戶點(diǎn)擊“+”或者“-”按鈕之后把計(jì)數(shù)值體現(xiàn)在網(wǎng)頁(yè)標(biāo)題上掐暮,這就是一個(gè)修改 DOM 的副作用操作,所以必須把 Counter 寫成 class政钟,而且添加下面的代碼:
介紹一下副作用(做了這件事情路克,我們還必須要再做一些事情)
我們寫的有狀態(tài)組件,通常會(huì)產(chǎn)生很多的副作用(side effect)养交,比如發(fā)起ajax請(qǐng)求獲取數(shù)據(jù)精算,添加一些監(jiān)聽(tīng)的注冊(cè)和取消注冊(cè),手動(dòng)修改dom等等碎连。我們之前都把這些副作用的函數(shù)寫在生命周期函數(shù)鉤子里灰羽,比如componentDidMount,componentDidUpdate和componentWillUnmount鱼辙。而現(xiàn)在的useEffect就相當(dāng)與這些聲明周期函數(shù)鉤子的集合體廉嚼。它以一抵三。同時(shí)倒戏,由于前文所說(shuō)hooks可以反復(fù)多次使用怠噪,相互獨(dú)立。所以我們合理的做法是杜跷,給每一個(gè)副作用一個(gè)單獨(dú)的useEffect鉤子傍念。這樣一來(lái),這些副作用不再一股腦堆在生命周期鉤子里葛闷,代碼變得更加清晰憋槐。
componentDidMount() {
document.title = `Count: ${this.state.count}`;
}
componentDidUpdate() {
document.title = `Count: ${this.state.count}`;
}
而有了 useEffect,我們就不用寫一個(gè) class 了淑趾,對(duì)應(yīng)代碼如下:
import { useState, useEffect } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${this.state.count}`;
});
return (
<div>
<div>{count}</div>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</div>
);
};
setEffect 的參數(shù)是一個(gè)函數(shù)秦陋,組件每次渲染之后,都會(huì)調(diào)用這個(gè)函數(shù)參數(shù)治笨,這樣就達(dá)到了 componentDidMount 和 componentDidUpdate 一樣的效果驳概。
雖然本質(zhì)上赤嚼,依然是 componentDidMount 和 componentDidUpdate 兩個(gè)生命周期被調(diào)用,但是現(xiàn)在我們關(guān)心的不是 mount 或者 update 過(guò)程顺又,而是“after render”事件更卒,useEffect 就是告訴組件在“渲染完”之后做點(diǎn)什么事。
讀者可能會(huì)問(wèn)稚照,現(xiàn)在把 componentDidMount 和 componentDidUpdate 混在了一起蹂空,那假如某個(gè)場(chǎng)景下我只在 mount 時(shí)做事但 update 不做事,用 useEffect 不就不行了嗎果录?
其實(shí)上枕,用一點(diǎn)小技巧就可以解決。useEffect 還支持第二個(gè)可選參數(shù)弱恒,只有同一 useEffect 的兩次調(diào)用第二個(gè)參數(shù)不同時(shí)辨萍,第一個(gè)函數(shù)參數(shù)才會(huì)被調(diào)用. 所以,如果想模擬 componentDidMount返弹,只需要這樣寫:
useEffect(() => {
// 這里只有mount時(shí)才被調(diào)用锈玉,相當(dāng)于componentDidMount
}, [123]);
在上面的代碼中,useEffect 的第二個(gè)參數(shù)是 [123]义起,其實(shí)也可以是任何一個(gè)常數(shù)拉背,因?yàn)樗肋h(yuǎn)不變,所以 useEffect 只在 mount 時(shí)調(diào)用第一個(gè)函數(shù)參數(shù)一次默终,達(dá)到了 componentDidMount 一樣的效果椅棺。
如果想執(zhí)行只運(yùn)行一次的 effect(僅在組件掛載和卸載時(shí)執(zhí)行),可以傳遞一個(gè)空數(shù)組([])作為第二個(gè)參數(shù)齐蔽。這就告訴 React 你的 effect 不依賴于 props 或 state 中的任何值两疚,所以它永遠(yuǎn)都不需要重復(fù)執(zhí)行。這并不屬于特殊情況 —— 它依然遵循輸入數(shù)組的工作方式肴熏。
如果你傳入了一個(gè)空數(shù)組(
[]
)鬼雀,effect 內(nèi)部的 props 和 state 就會(huì)一直持有其初始值。盡管傳入[]
作為第二個(gè)參數(shù)有點(diǎn)類似于componentDidMount
和componentWillUnmount
的思維模式蛙吏,但我們有 更好的 方式 來(lái)避免過(guò)于頻繁的重復(fù)調(diào)用 effect源哩。除此之外,請(qǐng)記得 React 會(huì)等待瀏覽器完成畫面渲染之后才會(huì)延遲調(diào)用useEffect
鸦做,因此會(huì)使得處理額外操作很方便励烦。
我們推薦啟用 eslint-plugin-react-hooks
中的 exhaustive-deps
規(guī)則。此規(guī)則會(huì)在添加錯(cuò)誤依賴時(shí)發(fā)出警告并給出修復(fù)建議泼诱。
1.3 useContext
用到的很少坛掠,暫時(shí)不做介紹。React Context API 大家都很少用到,有興趣的同學(xué)可以去了解一下屉栓。
提供了上下文(context)的功能
2. 簡(jiǎn)介
上面我們介紹了 useState
舷蒲、useEffect
和useContext
這三個(gè)最基本的 Hooks,可以感受到友多,Hooks 將大大簡(jiǎn)化使用 React 的代碼牲平。
首先我們可能不再需要 class了,雖然 React 官方表示 class 類型的組件將繼續(xù)支持域滥,但是纵柿,業(yè)界已經(jīng)普遍表示會(huì)遷移到 Hooks 寫法上,也就是放棄 class启绰,只用函數(shù)形式來(lái)編寫組件昂儒。
Hooks 發(fā)布后, 會(huì)帶來(lái)什么樣的改變呢委可? 毫無(wú)疑問(wèn)渊跋, 未來(lái)的組件, 更多的將會(huì)是函數(shù)式組件撤缴。
3. Custom React Hooks
我們還可以自定鉤子刹枉。這樣我們才能把可以復(fù)用的邏輯抽離出來(lái)叽唱,變成一個(gè)個(gè)可以隨意插拔的“插銷”屈呕,哪個(gè)組件要用來(lái),我就插進(jìn)哪個(gè)組件里棺亭,so easy虎眨!我們來(lái)看一個(gè)有關(guān)表單的例子。
從 Class 遷移到 Hook
-
constructor
:函數(shù)組件不需要構(gòu)造函數(shù)镶摘。你可以通過(guò)調(diào)用useState
來(lái)初始化 state嗽桩。如果計(jì)算的代價(jià)比較昂貴,你可以傳一個(gè)函數(shù)給useState
凄敢。 -
getDerivedStateFromProps
:改為 在渲染時(shí) 安排一次更新碌冶。
盡管你可能 不需要它,但在一些罕見(jiàn)的你需要用到的場(chǎng)景下涝缝,你可以在渲染過(guò)程中更新 state 扑庞。React 會(huì)立即退出第一次渲染并用更新后的 state 重新運(yùn)行組件以避免耗費(fèi)太多性能。
這里我們把 row prop 上一輪的值存在一個(gè) state 變量中以便比較:
function ScrollView({row}) {
const [isScrollingDown, setIsScrollingDown] = useState(false);
const [prevRow, setPrevRow] = useState(null);
if (row !== prevRow) {
// Row 自上次渲染以來(lái)發(fā)生過(guò)改變拒逮。更新 isScrollingDown罐氨。
setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
}
初看這或許有點(diǎn)奇怪,但渲染期間的一次更新恰恰就是 getDerivedStateFromProps 一直以來(lái)的概念滩援。
-
shouldComponentUpdate
:詳見(jiàn) 下方React.memo
. -
render
:這是函數(shù)組件體本身栅隐。 -
componentDidMount
,componentDidUpdate
,componentWillUnmount
:useEffect
Hook 可以表達(dá)所有這些(包括 不那么 常見(jiàn) 的場(chǎng)景)的組合。 -
getSnapshotBeforeUpdate
,componentDidCatch
以及getDerivedStateFromError
:目前還沒(méi)有這些方法的 Hook 等價(jià)寫法租悄,但很快會(huì)被添加谨究。
從 Class 遷移到 Hook
- 生命周期方法要如何對(duì)應(yīng)到 Hook?
- 我該如何使用 Hook 進(jìn)行數(shù)據(jù)獲绕濉记盒?
- 有類似實(shí)例變量的東西嗎?
- 我應(yīng)該使用單個(gè)還是多個(gè) state 變量外傅?
- 我可以只在更新時(shí)運(yùn)行 effect 嗎纪吮?
- 如何獲取上一輪的 props 或 state?
- 為什么我會(huì)在我的函數(shù)中看到陳舊的 props 和 state 萎胰?
- 我該如何實(shí)現(xiàn) getDerivedStateFromProps碾盟?
- 有類似 forceUpdate 的東西嗎?
- 我可以引用一個(gè)函數(shù)組件嗎技竟?
- 我該如何測(cè)量 DOM 節(jié)點(diǎn)冰肴?
- const [thing, setThing] = useState() 是什么意思?
參考
- https://zh-hans.reactjs.org/docs/thinking-in-react.html
- https://angular.io/guide/lifecycle-hooks
- https://cn.vuejs.org/v2/guide/instance.html#%E5%AE%9E%E4%BE%8B%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%92%A9%E5%AD%90
- http://react-china.org/t/react-v16-7-0-alpha-hooks/26839
- react 生命周期各版本對(duì)比
- React v15到v16.3, v16.4新生命周期總結(jié)以及使用場(chǎng)景
- React生命周期圖
- 全面了解 React 新功能: Suspense 和 Hooks
- custom-react-hooks
- https://upmostly.com/tutorials/react-hooks-simple-introduction/
- https://upmostly.com/tutorials/using-custom-react-hooks-simplify-forms/
- https://cdnjs.com/libraries/bulma
- https://mp.weixin.qq.com/s/rCCO3Dz20ihWL4eUl1Zijg