React報錯之React Hook useEffect has a missing dependency
- 2022 年 8 月 20 日
- 筆記
正文從這開始~
總覽
當useEffect
鉤子使用了一個我們沒有包含在其依賴數組中的變數或函數時,會產生”React Hook useEffect has a missing dependency”警告。為了解決該錯誤,禁用某一行的eslint
規則,或者將變數移動到useEffect
鉤子內。
這裡有個示例用來展示警告是如何發生的。
// 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>
);
}