2021-04-17 React Hooks - useEffect

React Hook

useEffect 就是一個 Effect Hook,給函數組件增加了操作副作用的能力。

它跟 class 組件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不過被合并成了一個 API。

1. 無需清除的effect

// 在組件didMount和didUpdate的時候 都會執行該方法
import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

2. useEffect 做了什么?

通過使用這個 Hook,你可以告訴 React 組件需要在渲染后執行某些操作。React 會保存你傳遞的函數(我們將它稱之為 “effect”),并且在執行 DOM 更新之后調用它。在這個 effect 中,我們設置了 document 的 title 屬性,不過我們也可以執行數據獲取或調用其他命令式的 API。

3. 為什么在組件內部調用 useEffect?

將 useEffect 放在組件內部讓我們可以在 effect 中直接訪問 count state 變量(或其他 props)。我們不需要特殊的 API 來讀取它 —— 它已經保存在函數作用域中。

Hook 使用了 JavaScript 的閉包機制,而不用在 JavaScript 已經提供了解決方案的情況下,還引入特定的 React API。

4. useEffect 會在每次渲染后都執行嗎?

是的,默認情況下,它在第一次渲染之后*****和*****每次更新之后都會執行。(我們稍后會談到如何控制它。)你可能會更容易接受 effect 發生在“渲染之后”這種概念,不用再去考慮“掛載”還是“更新”。

React 保證了每次運行 effect 的同時,DOM 都已經更新完畢。

傳遞給 useEffect 的函數在每次渲染中都會有所不同,這是刻意為之的。事實上這正是我們可以在 effect 中獲取最新的 count 的值,而不用擔心其過期的原因。

每次我們重新渲染,都會生成新的 effect,替換掉之前的。某種意義上講,effect 更像是渲染結果的一部分 —— 每個 effect “屬于”一次特定的渲染。

5. 需要清除的effect

為什么要在 effect 中返回一個函數?

這是 effect 可選的清除機制。每個 effect 都可以返回一個清除函數。如此可以將添加和移除訂閱的邏輯放在一起。它們都屬于 effect 的一部分。

React 何時清除 effect?

React 會在組件卸載的時候執行清除操作。effect 在每次渲染的時候都會執行。

 這就是為什么 React *會*在執行當前 effect 之前對上一個 effect 進行清除。我們稍后將討論[為什么這將助于避免 bug](https://react.docschina.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update)以及[如何在遇到性能問題時跳過此行為](https://react.docschina.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects)。

Hook 允許我們按照代碼的用途分離他們, 而不是像生命周期函數那樣。React 將按照 effect 聲明的順序依次調用組件中的每一個 effect。

6. 解釋: 為什么每次更新的時候都要運行 Effect

如果你已經習慣了使用 class,那么你或許會疑惑為什么 effect 的清除階段在每次重新渲染時都會執行,而不是只在卸載組件的時候執行一次。

// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange);     // 運行第一個 effect

// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 清除上一個 effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange);     // 運行下一個 effect

// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 清除上一個 effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange);     // 運行下一個 effect

// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 清除最后一個 effect

通過Effect進行性能優化

在某些情況下,每次渲染后都執行清理或者執行 effect 可能會導致性能問題。在 class 組件中,我們可以通過在 componentDidUpdate 中添加對 prevProps 或 prevState 的比較邏輯解決:

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

通知 React 跳過對 effect 的調用,只要傳遞數組作為 useEffect 的第二個可選參數即可:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 僅在 count 更改時更新


useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // 僅在 props.friend.id 發生變化時,重新訂閱

注意:

如果你要使用此優化方式,請確保數組中包含了所有外部作用域中會隨時間變化并且在 effect 中使用的變量,否則你的代碼會引用到先前渲染中的舊變量。

如果想執行只運行一次的 effect(僅在組件掛載和卸載時執行),可以傳遞一個空數組([])作為第二個參數。這就告訴 React 你的 effect 不依賴于 props 或 state 中的任何值,所以它永遠都不需要重復執行。這并不屬于特殊情況 —— 它依然遵循依賴數組的工作方式。

推薦啟用 eslint-plugin-react-hooks 中的 exhaustive-deps 規則。此規則會在添加錯誤依賴時發出警告并給出修復建議。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374