五種有趣的 UseEffect 無限循環類型
一般來說,無限循環被認為是不好的做法。但在一些邊界 case 中,你沒有任何選擇,只能選擇無限循環。了解 React 的無限循環模式是件好事。
當無限循環沒有無法停止時,最終瀏覽器會殺死你的代碼正在運行的標簽。所以不要使用沒有任何斷點的 “無限循環”。
useEffect
useEffect hook 允許我們在一個組件中表現出副作用。當 hooks 被引入 react 16 時,useEffect hooks 比其他 hooks 更有吸引力。因為它提供了 componentDidMount、componentDidUpdate 和 componentWillUnmount 生命周期方法的綜合功能。
只有當依賴關系被改變時,useEffect hook 才會觸發回調函數。而且它使用淺比較法來比較 hooks 的值。
你可以把 useEffect 看作是一塊能量石,它是一塊最強大的石頭,如果你沒有正確地處理它,這塊石頭會摧毀你。
缺失依賴
缺失依賴關系的 useEffect 通常被認為是一種不好的做法,所以總是盡量避免它。
思考一下下面的代碼,它將一直調用 API。
- useEffect(() => {
- fetch("/api/user")
- .then((res) => res.json)
- .then((res) => {
- setData(res);
- });
- });
會發生什么
如果 useEffect 只有在依賴關系發生變化時才觸發回調,那為什么我們在這里會出現無限循環?
你需要考慮到 React 的另一個重要的法則,即 “當 state 或 props 發生變化時,組件將重新渲染”。
在這段代碼中,我們使用 setData 在網絡調用成功后設置狀態值,它將觸發組件的重新渲染。由于 useEffect 沒有值可以比較,所以它將調用回調。
Fetch 將再次觸發setData,setData將觸發組件重新渲染,如此反復。
如何修復這個問題?
我們需要將依賴指定為空數組。
- useEffect(() => {
- fetch("/api/user")
- .then((res) => res.json)
- .then((res) => {
- setData(res);
- });
- }, []); // <- dependencies
根據官方文檔,省略依賴關系是不安全的
依賴性的作用
useEffect 使用淺層對象比較來確定數據是否被改變。因為奇怪的 JavaScript 條件判斷系統 ??。
- var mark1 = function () {
- return "100";
- }; // 唯一的對象引用
- var mark2 = function () {
- return "100";
- }; // 唯一的對象引用
- mark1 == mark2; // false
- mark1 === mark2; // false
讓我們看看以下代碼
- import React, { useCallback, useEffect, useState } from "react";
- export default function App() {
- const [count, setCount] = useState(0);
- const getData = () => {
- return window.localStorage.getItem("token");
- };
- const [dep, setDep] = useState(getData());
- useEffect(() => {
- setCount(count + 1);
- }, [getData]);
- return (
- <div className="App">
- <h1>Hello CodeSandbox</h1>
- <button onClick={() => setCount(count + 1)}>{count}</button>
- <h2>Start editing to see some magic happen!</h2>
- </div>
- );
- }
函數 getData 作為依賴項被傳入。
當你運行這段代碼時,它將拋出 “超過最大更新” 的錯誤,這意味著代碼有一個無限循環。
發生了什么?
由于 useEffect 使用淺層比較法來比較數值。該函數的淺層比較將總是給出 false。
如何修復這個問題?
為了修復這個問題,我們需要使用另一個叫做 useCallback 的 hook。
useCallback 返回一個memoized 版本的回調,只在依賴關系改變時才會改變。
- const getData = useCallback(() => {
- return window.localStorage.getItem("token");
- }, []); // <- dependencies
將數組作為依賴
你可能知道,二者的淺層比較總是假的,所以以數組形式傳遞依賴關系也會導致 “無限循環”。
讓我們看看以下代碼
- import React, { useCallback, useEffect, useState } from "react";
- export default function App() {
- const [count, setCount] = useState(0);
- const dep = ["a"];
- const [value, setValue] = useState(["b"]);
- useEffect(() => {
- setValue(["c"]);
- }, [dep]);
- return (
- <div className="App">
- <h1>Hello CodeSandbox</h1>
- <button onClick={() => setCount(count + 1)}>{count}</button>
- <h2>Start editing to see some magic happen!</h2>
- </div>
- );
- }
這里,數組 dep 作為 useEffect 的依賴傳入。
當你運行這段代碼時,瀏覽器控制臺會拋出這個錯誤。
發生了什么?
兩個數組的淺層比較總是假的,所以 useEffect 總是會觸發回調。
如果修復這個問題?
由于 useCallback 的返回是一個函數,我們不能使用。
那么,我們應該怎么辦?
我們需要使用另一個名為 useRef 的 hook
useRef 返回一個可變的對象,.current 具有初始值。
將對象作為依賴
你可能會猜到兩個對象的淺層比較總是假的,所以 useEffect 會一直觸發回調。
讓我們看看一下這段代碼
- import React, { useEffect, useState, useRef } from "react";
- export default function Home() {
- const [value, setValue] = useState(["b"]);
- const { current: a } = useRef(["a"]);
- useEffect(() => {
- setValue(["c"]);
- }, [a]);
- }
data 是作為 useEffect 的依賴項傳入的。
當你運行這段代碼時,你的瀏覽器控制臺將被拋出一個無限循環的錯誤。
這里發生了什么?
對象的淺層比較將永遠是假的,所以它將觸發 useEffect 的回調。
如果修復這個問題?
如果我們將依賴關系備忘化,我們就能打破無限循環。那么,如何做到這一點呢?
是的,我們將使用另一個名為 useMemo 的 hook。
useMemo 只有在依賴關系發生變化時才會重新計算記憶化的值。
- import React, { useMemo, useEffect, useState } from "react";
- export default function App() {
- const [count, setCount] = useState(0);
- const data = useMemo(
- () => ({
- is_fetched: false,
- }),
- []
- ); // <- dependencies
- useEffect(() => {
- setCount(count + 1);
- }, [data]);
- return (
- <div className="App">
- <h1>Hello CodeSandbox</h1>
- <button onClick={() => setCount(count + 1)}>{count}</button>
- <h2>Start editing to see some magic happen!</h2>
- </div>
- );
- }
錯誤的依賴
錯誤的依賴關系與 React 無關,甚至與 javascript 無關。當使用錯誤的依賴關系時,我們必須承擔起責任。
讓我們看看一下這段代碼
- import React, { useEffect, useState } from "react";
- const App = () => {
- const [text, setText] = useState("");
- useEffect(() => {
- setText(text);
- }, [text]);
- return null;
- };
- export default App;
我希望沒有必要解釋這個問題模式和它的修復方法。如果你想知道解釋和修復方法,請在評論中告訴我。
注意:有很多方法可以避免 React 組件內部的無限循環,我只提到了幾種方法。
總是使用 eslint-plugin-react-hooks 或 create-react-app,它將幫助你在這些問題進入生產環境之前找到這些問題。
一家公司在一周內因無限循環而損失了 $ 7.2 w。
所以在使用 useEffect 的時候一定要特別小心。
原文:https://javascript.plainenglish.io/5-useeffect-infinite-loop-patterns-2dc9d45a253f
本文轉載自微信公眾號「前端鐵蛋」,可以通過以下二維碼關注。轉載本文請聯系前端鐵蛋公眾號。