淺談Hooks&&生命周期(2019-03-12)

生命周期

現(xiàn)在流行的前端框架,無(wú)論是angular還是React赋兵,又或是Angular2以及以上庄拇,都由框架自身提供了生命周期(有的叫生命周期鉤子)供開(kāi)發(fā)者使用癞尚。

下面我們看下上面幾個(gè)框架的生命周期:

Vue生命周期:


Vue-lifecycle

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-Lifecycle1
React-Lifecycle2

React生命周期(16.0后):

React-Lifecycle3

我們下面看一個(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 生命周期使用小提示:

  1. getDerivedStateFromProps被React官方歸類為不常用的生命周期憾朴,能不用就盡量不用狸捕,前面用那么多篇幅講這個(gè)生命周期主要是為了加深對(duì)Reac運(yùn)行機(jī)制的理解。
  2. unsafe

下面開(kāi)始咱們今天的主題Hooks众雷。

Hooks

React v16.7.0-alpha 中第一次引入了 Hooks 的概念灸拍, 為什么要引入這個(gè)東西呢?

有兩個(gè)原因:

  1. React 官方覺(jué)得 class組件太難以理解砾省,OO(面向?qū)ο螅┨y懂了
  2. 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)上的截圖:

image.png

乍一看還是挺多的铅祸, 其實(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ò)亂了挠说。

條件渲染報(bào)錯(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)類似于 componentDidMountcomponentWillUnmount 的思維模式蛙吏,但我們有 更好的 方式 來(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舷蒲、useEffectuseContext這三個(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, componentWillUnmountuseEffect Hook 可以表達(dá)所有這些(包括 不那么 常見(jiàn) 的場(chǎng)景)的組合。
  • getSnapshotBeforeUpdatecomponentDidCatch 以及 getDerivedStateFromError:目前還沒(méi)有這些方法的 Hook 等價(jià)寫法租悄,但很快會(huì)被添加谨究。

從 Class 遷移到 Hook

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末榔组,一起剝皮案震驚了整個(gè)濱河市熙尉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌搓扯,老刑警劉巖检痰,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異锨推,居然都是意外死亡铅歼,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門换可,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)椎椰,“玉大人,你說(shuō)我怎么就攤上這事沾鳄】” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵译荞,是天一觀的道長(zhǎng)瓤的。 經(jīng)常有香客問(wèn)我,道長(zhǎng)磁椒,這世上最難降的妖魔是什么堤瘤? 我笑而不...
    開(kāi)封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮浆熔,結(jié)果婚禮上本辐,老公的妹妹穿的比我還像新娘桥帆。我一直安慰自己,他們只是感情好慎皱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布老虫。 她就那樣靜靜地躺著,像睡著了一般茫多。 火紅的嫁衣襯著肌膚如雪祈匙。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天天揖,我揣著相機(jī)與錄音夺欲,去河邊找鬼。 笑死今膊,一個(gè)胖子當(dāng)著我的面吹牛些阅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播斑唬,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼市埋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了恕刘?” 一聲冷哼從身側(cè)響起缤谎,我...
    開(kāi)封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎褐着,沒(méi)想到半個(gè)月后坷澡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡献起,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年洋访,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了镣陕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谴餐。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖呆抑,靈堂內(nèi)的尸體忽然破棺而出岂嗓,到底是詐尸還是另有隱情,我是刑警寧澤鹊碍,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布厌殉,位于F島的核電站,受9級(jí)特大地震影響侈咕,放射性物質(zhì)發(fā)生泄漏公罕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一耀销、第九天 我趴在偏房一處隱蔽的房頂上張望楼眷。 院中可真熱鬧,春花似錦、人聲如沸罐柳。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)张吉。三九已至齿梁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肮蛹,已是汗流浹背勺择。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伦忠,地道東北人酵幕。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像缓苛,于是被迫代替她去往敵國(guó)和親芳撒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容