React報錯之React Hook useEffect has a missing dependency

  • 2022 年 8 月 20 日
  • 筆記

正文從這開始~

總覽

useEffect鉤子使用了一個我們沒有包含在其依賴數組中的變數或函數時,會產生”React Hook useEffect has a missing dependency”警告。為了解決該錯誤,禁用某一行的eslint規則,或者將變數移動到useEffect鉤子內。

react-hook-useeffect-has-missing-dependency.png

這裡有個示例用來展示警告是如何發生的。

// App.js

import React, {useEffect, useState} from 'react';

export default function App() {
  const [address, setAddress] = useState({country: '', city: ''});

  // 👇️ objects/arrays are different on re-renders
  const obj = {country: 'Chile', city: 'Santiago'};

  useEffect(() => {
    setAddress(obj);
    console.log('useEffect called');

    // ⛔️ React Hook useEffect has a missing dependency: 'obj'.
    // Either include it or remove the dependency array. eslintreact-hooks/exhaustive-deps
  }, []);

  return (
    <div>
      <h1>Country: {address.country}</h1>
      <h1>City: {address.city}</h1>
    </div>
  );
}

上述程式碼片段的問題在於,我們在useEffect鉤子內部使用了obj變數,但我們沒有在其依賴數組中包含該變數。

最明顯的解決方法是將obj變數添加到useEffect鉤子的依賴數組中。然而,在本例中,它將導致一個錯誤,因為在JavaScript中,對象和數組是通過引用進行比較的。

obj變數是一個對象,在每次重新渲染時都有相同的鍵值對,但它每次都指向記憶體中的不同位置,所以它將無法通過相等檢查並導致無限的重新渲染循環。

在JavaScript中,數組也是通過引用進行比較。

禁用規則

繞過”React Hook useEffect has a missing dependency”警告的一個方法是禁用某一行的eslint規則。

import React, {useEffect, useState} from 'react';

export default function App() {
  const [address, setAddress] = useState({country: '', city: ''});

  const obj = {country: 'Chile', city: 'Santiago'};

  useEffect(() => {
    setAddress(obj);
    console.log('useEffect called');

  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <div>
      <h1>Country: {address.country}</h1>
      <h1>City: {address.city}</h1>
    </div>
  );
}

依賴數組上方的注釋禁用了單行的react-hooks/exhausting-deps規則。

useEffect鉤子的第二個參數傳遞的是空數組時,只有當組件掛載或者卸載時才會調用。

依賴移入

另一種解決辦法是,將變數或者函數聲明移動到useEffect鉤子內部。

import React, {useEffect, useState} from 'react';

export default function App() {
  const [address, setAddress] = useState({country: '', city: ''});

  useEffect(() => {
    // 👇️ move object / array / function declaration
    // inside of the useEffect hook
    const obj = {country: 'Chile', city: 'Santiago'};

    setAddress(obj);
    console.log('useEffect called');
  }, []);

  return (
    <div>
      <h1>Country: {address.country}</h1>
      <h1>City: {address.city}</h1>
    </div>
  );
}

我們把對象的變數聲明移到了useEffect鉤子裡面。這就消除了警告,因為鉤子不再依賴對象,對象聲明在鉤子內部。

依賴移出

另一個可能的解決方案是將函數或變數的聲明移出你的組件,這可能很少使用,但最好知道。

import React, {useEffect, useState} from 'react';

// 👇️ move function/variable declaration outside of component
const obj = {country: 'Chile', city: 'Santiago'};

export default function App() {
  const [address, setAddress] = useState({country: '', city: ''});

  useEffect(() => {
    setAddress(obj);
    console.log('useEffect called');
  }, []);

  return (
    <div>
      <h1>Country: {address.country}</h1>
      <h1>City: {address.city}</h1>
    </div>
  );
}

這是很有用的,因為每次重新渲染App組件時,變數不會每次都重新創建。該變數在所有渲染中都會指向記憶體的相同位置,因此useEffect不需要在其依賴數組中跟蹤它。

useMemo

另一個解決方案是使用useMemo鉤子來得到一個記憶值。

import React, {useMemo, useEffect, useState} from 'react';

export default function App() {
  const [address, setAddress] = useState({country: '', city: ''});

  // 👇️ get memoized value
  const obj = useMemo(() => {
    return {country: 'Chile', city: 'Santiago'};
  }, []);

  useEffect(() => {
    setAddress(obj);
    console.log('useEffect called');

    // 👇️ safely include in dependencies array
  }, [obj]);

  return (
    <div>
      <h1>Country: {address.country}</h1>
      <h1>City: {address.city}</h1>
    </div>
  );
}

我們使用useMemo鉤子得到一個記憶值,該值在渲染期間不會改變。

useMemo鉤子接收一個函數,該函數返回一個要被記憶的值和一個依賴數組作為參數。該鉤子只有在其中一個依賴項發生變化時才會重新計算記憶值。

useCallback

請注意,如果你正在使用一個函數,你將使用useCallback鉤子來獲得一個在渲染期間不會改變的記憶回調。

import React, {useMemo, useEffect, useState, useCallback} from 'react';

export default function App() {
  const [address, setAddress] = useState({country: '', city: ''});

  // 👇️ get memoized callback
  const sum = useCallback((a, b) => {
    return a + b;
  }, []);

  // 👇️ get memoized value
  const obj = useMemo(() => {
    return {country: 'Chile', city: 'Santiago'};
  }, []);

  useEffect(() => {
    setAddress(obj);
    console.log('useEffect called');

    console.log(sum(100, 100));

    // 👇️ safely include in dependencies array
  }, [obj, sum]);

  return (
    <div>
      <h1>Country: {address.country}</h1>
      <h1>City: {address.city}</h1>
    </div>
  );
}

useCallback鉤子接收一個內聯回調函數和一個依賴數組,並返回一個記憶化版本的回調,該回調只在其中一個依賴發生變化時才會改變。

如果這些建議對你都不起作用,你總是可以用注釋來消滅警告。

import React, {useEffect, useState} from 'react';

export default function App() {
  const [address, setAddress] = useState({country: '', city: ''});

  const obj = {country: 'Chile', city: 'Santiago'};

  useEffect(() => {
    setAddress(obj);
    console.log('useEffect called');

  // 👇️ disable the rule for a single line

  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return (
    <div>
      <h1>Country: {address.country}</h1>
      <h1>City: {address.city}</h1>
    </div>
  );
}