- useState 和 useEffect 集中了 React 函數式元件的大部分狀態和副作用,取代了許多經典的生命週期方法。
- 效果可以包含傳回函數的清理邏輯,使您可以管理訂閱、間隔和其他外部資源而不會發生記憶體洩漏。
- 依賴陣列控制每個 effect 的執行時間,並且必須包含其中使用的所有響應式值,以平衡正確性和效能。
- 將邏輯拆分成幾個小的 useEffect,並按職責進行劃分,可以提高可讀性,方便重用,並減少無限渲染或不正確的依賴項等常見錯誤。

如果你每天都使用 React,那麼掌握 useState 和 useEffect 就是必不可少的。這幾乎是每天都會用到的。這兩個 Hook 處理了現代應用程式的大部分狀態和副作用邏輯,從簡單的計數器到包含 HTTP 請求、事件訂閱和動畫的複雜介面,無所不能。
問題在於,很容易「憑感覺」使用它們,最終導致記憶體洩漏、無休止的渲染,或在不應該觸發的時候觸發特效。在本文中,我們將冷靜地講解如何正確使用 useState,尤其是 useEffect:它的具體作用、它與組件生命週期的關係、如何管理清理、如何使用依賴數組優化性能,以及如何在不費腦筋的情況下應用高級模式。
什麼是 Hooks?為什麼 useState 和 useEffect 如此重要?
React 16.8 中引入了 Hooks,允許在無需使用類別的情況下使用狀態和其他功能。而不是從 React.Component 在處理生命週期方法時,我們會使用呼叫 Hooks 的函數,例如 useState y useEffect 裡面。
useState 是一個 Hook,它允許你在函數元件中取得本機狀態。它會傳回一個值和一個用於更新該值的函數,每次更新該狀態時,都會觸發元件的新渲染。它是實現介面動態化的基礎:計數器、表單、載入標誌等等。
另一方面,useEffect 是一個 Hook,它允許你在渲染後執行輔助效果。這包括資料請求、WebSocket訂閱、DOM監聽器、計時器、與外部庫的集成,或在沒有其他選擇時手動更新DOM等操作。
如果你之前使用過類別元件,你可以把 useEffect 看作是 componentDidMount、componentDidUpdate 和 componentWillUnmount 的組合。但所有功能都封裝在一個聲明式 API 中。其理念是考慮“渲染後發生的效果”,而不是單獨的“組裝”或“更新”階段。
useEffect 在概念層面上的工作原理
基本的 useEffect 簽名非常簡單: useEffect(configuración, dependencias?)你可以向它傳遞一個配置函數,該函數將在元件繪製完成後運行;也可以選擇傳遞一個依賴項列表,該列表決定何時重複該效果。
設定函數集中了你的效果邏輯:訂閱某些內容、發起請求、啟動間隔、修改 DOM 等。 函數還可以傳回另一個清理函數,React 會在需要撤銷你所做的操作時執行該函數:取消時間間隔、取消訂閱、中止請求等。
useEffect 的第二個參數是著名的依賴陣列。你需要在這裡列出所有在 effect 中使用的「響應式」值:props、state,以及直接在元件主體中定義的任何變數或函數。 React 會將該陣列與上一次渲染的陣列進行比較。 Object.is如果任何依賴項發生更改,請清除舊的 effect 並執行新的 effect。
如果沒有傳遞依賴項數組,useEffect 會在每次渲染後執行。如果你傳遞一個空數組 []此效果僅在組裝時執行一次,拆卸時即清除,模擬扭力作用。 componentDidMount / componentWillUnmount如果傳遞特定的依賴項,則只有當其中一個值發生變化時,效果才會觸發。
第一步:結合 useState 和 useEffect
要理解這兩個 Hook 之間的關係,一個經典的例子是更新文件標題的典型計數器。。 用 useState 您可以控制點擊次數,並且 useEffect 你改變了 document.title 每次渲染後:
關鍵在於 useEffect 是在元件內部定義的。因此,借助 JavaScript 閉包,您可以直接讀取當前狀態和屬性。您無需使用特殊的 React API 即可在 effect 中存取狀態;一切都在函數的作用域內。
此外,React 不會在渲染之間重複使用同一個 effect 函數:每次渲染都會產生新的函數。這是有意為之:每個 effect 都與其創建時的渲染事件相關聯,當 React 檢測到依賴項發生變化時,它會先清理舊 effect,然後再用新 effect 替換舊 effect。這種模型使得理解每個 effect 在任何給定時間點所看到的值變得更加容易。
另一個重要的細節是,這些特效不會阻礙介面繪製。React 先更新 DOM 並讓瀏覽器渲染,然後執行 useEffect對於大多數情況(請求), 日誌等等。 )這可以提升流暢感。如果您需要在使用者看到螢幕之前同步執行某些操作(例如,測量並重新定位工具提示),則應使用 useLayoutEffect它共享 API,但在繪製之前運行。
效果類型:清潔前後的效果
並非所有副作用都一樣:有些副作用運行完畢就結束了,而有些副作用則需要明確清理。區分這兩種類型是避免記憶中留下未解決的問題或奇怪行為的關鍵。
免清洗效果是指不留下任何「開放」狀態的效果。例如,寫入日誌、發出一次性請求並將結果儲存到狀態、啟動自管理動畫或進行一次性 DOM 修改。就生命週期而言,它們僅依賴 componentDidMount / componentDidUpdate 他們什麼都不要求。 componentWillUnmount.
一個典型的例子是使用 navigator.geolocation.getCurrentPosition 元件渲染完成後你請求位置訊息,用經緯度更新狀態,React 重新渲染,就完成了。你沒有留下永久監聽器,所以卸載組件時不需要清理任何東西。
當您的組件持續連接到外部系統時,清潔效果就會顯現。這些包括 WebSocket 或聊天 API 的訂閱、DOM 事件監聽器、重複間隔或逾時等。在這些情況下,如果您在卸載或更改依賴項時不進行清理,最終會導致記憶體洩漏或意外行為。
React 處理這種清理工作的慣用方法是從 effect 傳回一個函數。此函數會在每次需要「卸載」效果時執行:例如元件從 DOM 移除,或依賴項發生變更且需要以新效果取代舊效果時。一個典型的例子是帶有計數器的組件。 setInterval 用…清洗 clearInterval 效果回歸。
useEffect 應用於全域事件和訂閱
useEffect 的一個非常常見的用途是訂閱全域瀏覽器事件。例如 resize 德爾 window o keydown 德爾 document在這種情況下,您需要在設定組件時連接到事件,並在拆卸組件時斷開連接,以避免監聽器累積。
模式始終相同:在效果器中新增監聽器,並在清理函數中移除它們。此外,如果您希望該訂閱只建立一次,則可以傳遞空的依賴項陣列。 [] 這樣就不會在每次渲染中重複出現。
同樣的方法也適用於與外部 API、第三方 SDK 或任何強制您在元件可見時「連接」的系統進行整合。你在 effect 設定函數中連接,在清理函數中斷開連接,並相信 React 會在依賴項更改時根據需要調用這兩個函數。
在開發模式和嚴格模式下,React 會在掛載後立即執行額外的配置和清理操作。這是一種壓力測試,旨在確保你的清理邏輯能夠真正撤銷配置所做的所有更改。如果你只在開發環境中發現異常行為,通常是因為你的清理工作做得不夠完善。
使用 useEffect 處理 HTTP 請求和競態條件
useEffect 的另一個非常常見的應用場景是,當某些屬性或狀態的一部分發生變化時,發出 HTTP 請求。例如,當用戶資料發生變化時會載入該用戶資料。 userId 或當道具中的名稱更新時,檢索有關寶可夢的資訊。
總體思路是:定義一個觸發非同步函數的效果,用接收到的資料更新狀態,並在依賴項數組中指定該請求所依賴的值。如果您只想在騎乘時觸發,請跳過此步驟。 []如果你希望它在每次內容更改時都運行的話。 userId包括 userId 在數組中。
一個需要注意的細節是,如果在請求回應之前移除元件會發生什麼情況。. 如果在 then 或之後 await 致電 setState 對於不再安裝的元件,您可能會遇到警告,或者更糟的是,會收到有關殭屍元件的狀態更新。
避免這種情況的常見方法是使用內部標誌(例如, isMounted o ignore)你在效果中定義它,你把它放進去 true在清潔功能中,您將其設定為 false在非同步函數內部,就在呼叫之前 setState你要檢查該標誌是否仍處於開啟狀態。 true如果已經是 false您跳過了更新步驟。這也有助於避免競態條件,即多個請求的解析順序可能與提交順序不同。
另一個更現代的選擇是,如果您使用的 API 支援 AbortController,則可以使用它。因此,清理函數會調用 abort() 並且,請求的承諾被拒絕,而沒有試圖影響狀態。無論如何,取消或忽略回應的邏輯必須蘊含在效果本身之中。
同一元件中多次呼叫 useEffect
Hooks 的一個優點是,它允許你根據邏輯的功能來分離邏輯,而不是根據邏輯需要放置的生命週期方法來分離邏輯。使用多個時沒有任何限制。 useEffect 在同一組件中。
這意味著你可以使用一個效果來管理文件標題,另一個效果來訂閱聊天,還有一個效果來監聽滾動事件。React 會依照元件中出現的順序,依序執行每個元件及其相依性和清理邏輯。
與類別(class)相比,類別中許多不相關的邏輯最終混雜在同一個生命週期方法中,而 Hooks 鼓勵你按職責劃分。一個效果對應一個特定目的。如果一個效果開始產生多種不同的效果,那麼可能就應該將其分割成幾個效果。
當把這些效果封裝在自訂鉤子中時,這種模式會變得更加強大。 (例如, useWindowSize, useChatStatus等等),但即使不做到那一步,使用多個小的效果而不是一個巨大的效果也能提高可讀性。
依賴項列表:響應式和效能
依賴項的集合既是 useEffect 的強大之處,也是許多麻煩的根源。整體規則很簡單:在 effect 中使用的每個響應式值都必須出現在該陣列中。響應式值包括 props、state 以及組件中定義的變數或函數。
這不是要「選擇」依賴項來減少效果的運行時間,而是要誠實地描述效果的邏輯依賴於哪些值。如果短切 eslint-plugin-react-hooks 它配置正確;當您遺漏了某些內容或添加了不必要的內容時,它會發出警報。
如果你想移除一個依賴項,你不能透過消除警告來實現;你需要修改程式碼,使該值不再具有回應性。例如,如果您使用常數 serverUrl 由於它始終保持不變,您可以將其移出組件主體。這樣,它就不再是響應式值,您可以將其從陣列中移除而不會誤導 React。
當一個效果讀取到一個回應值時,你通常會希望它能對該回應值的變化做出反應。這意味著將其添加到數組中。但有時您可能只想讀取某個物件(例如購物車)的「當前」值,而不想讓該物件發生任何更改而再次觸發 effect。針對這種情況,React 引入了「effect 事件」的概念(例如…)。 useEffectEvent),它封裝了非響應式程式碼,可以讀取最後一個值而不會成為依賴項。
為了提高效能,通常的做法是將依賴關係數組限制在最小範圍內。與其讓 effect 沒有第二個參數(這樣每次渲染後都會觸發它),不如傳遞一個只包含真正需要觸發它的值的陣列。如果渲染之間沒有相依性發生變化,React 會跳過清理和設定步驟,從而避免 effect 重新執行。
useEffect 常見問題排除
最常見的錯誤之一是導致效果陷入無限循環。這種情況通常發生在你更新 effect 本身的狀態時,而該狀態又是依賴陣列的一部分。 effect 改變了狀態,狀態的改變觸發了渲染,渲染又重新執行了 effect,如此循環往復。
要打破這個循環,請回顧一下你為什麼要從 effect 更新該狀態。如果你不與外部系統同步,甚至可能不需要使用 useEffect:很多事情都可以在渲染過程中直接處理。如果你要與外部系統同步,請確保狀態更新僅依賴實際的變化,而不是每次都觸發。
另一個常見的問題來源是依賴項會隨著每次渲染而改變,而你卻沒有註意到。在 JSX 中建立內聯物件或函數並在 effect 中使用它們總是會導致不同的依賴數組,因為每次渲染都會產生新的參考。為了避免這種情況,您可以將這些物件的創建移到 effect 本身,或者作為最後的手段,使用 `resolve` 來穩定它們。 useMemo / useCallback.
令人驚訝的是,即使組件仍在螢幕上,清潔功能也會運作。這是正常現象:每次依賴項發生變化時,React 都會在應用新的 effect 之前清理先前的效果。在開發環境中,啟用嚴格模式 (StrictMode) 後,它還會在掛載後立即執行額外的配置和清理操作。
最後,如果你的效果是視覺效果,並且在效果生效前註意到有閃爍,請檢查效果是否生效。你可能需要切換到 useLayoutEffect 強制它在瀏覽器渲染之前運行。這是一個需要謹慎使用的工具,僅在確實需要阻止渲染直到測量或定位完成時才使用。
useEffect 和伺服器端渲染 (SSR)
伺服器端渲染應用程式的一個重要細節是,特效僅在客戶端運行。在伺服器端渲染 (SSR) 期間,React 會產生初始 HTML,但不會運作效果。這些效果會在應用程式載入到瀏覽器後觸發。
這表示 useEffect 中任何依賴瀏覽器 API(例如 localStorage o window) 能夠抵禦伺服器攻擊因為它不會直接在那裡運行。你需要確保的是,第一次渲染(在伺服器端完成,然後在客戶端重複執行的渲染)是確定性的,並且不會讀取任何僅存在於瀏覽器中的內容。
如果你想在伺服器端和客戶端顯示不同的內容,常見的模式是使用狀態,例如: didMount最初它在 false,並且在 useEffect 同 [] 你把它穿上 true當該值為假時,您可以渲染元件的「安全」版本;當該值為真時,您就可以從中讀取資料了。 localStorage調整 DOM 等。但是,盡量確保視覺變化不會對網路連線速度慢的使用者造成太大影響。
一般來說,組件樹與環境的獨立性越強,將 SSR 與 Hooks 結合使用時遇到的意外情況就越少。請將 useEffect 保留給與外部系統的實際集成,這才是它的真正用途。
掌握 useState 和 useEffect 需要理解元件在每次渲染時都會“重新創建”,效果屬於特定的生命週期,依賴數組描述了程式碼和響應式資料之間的關係。當你內化了這種思維模型,那種神秘莫測的感覺就會消失,你就能輕鬆地使用這些 Hooks,編寫出更具聲明性、更簡潔、更易於維護的組件,即使是在大型 React 應用程式中也是如此。
對字節世界和一般技術充滿熱情的作家。我喜歡透過寫作分享我的知識,這就是我在這個部落格中要做的,向您展示有關小工具、軟體、硬體、技術趨勢等的所有最有趣的事情。我的目標是幫助您以簡單有趣的方式暢遊數位世界。