理解 React 中的 exhaustive-deps Eslint 规则
Understanding the exhaustive-deps Eslint rule in React
“react-hooks/exhaustive-deps”规则会在效果挂钩中缺少依赖项时向我们发出警告。
要消除警告,请将函数或变量声明移到挂钩内useEffect
,记住每次渲染时更改的数组和对象或禁用规则。
这是警告如何引起的示例。
import React, {useEffect, useState} from 'react'; export default function App() { const [address, setAddress] = useState({country: '', city: ''}); // 👇️ objects/arrays are different on re-renders // they are compared by reference (not by contents) const obj = {country: 'Germany', city: 'Hamburg'}; 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> ); }
问题是我们正在使用useEffectobj
钩子内部的变量
,但我们没有将它包含在 dependencies 数组中。
最明显的错误解决方案是将obj
变量添加到挂钩的依赖项数组中useEffect
。
但是,在这种情况下,它会导致错误,因为对象和数组在 JavaScript 中是通过引用进行比较的。
该obj
变量是一个在每次重新渲染时具有相同键值对的对象,但它每次都指向内存中的不同位置,因此它会导致相等性检查失败并导致无限重新渲染
循环。
禁用 Eslint 规则
解决
React Hook useEffect 缺少依赖项警告的一种方法
是禁用单行或整个文件的 Eslint 规则。
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: 'Germany', city: 'Hamburg'}; 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> ); }
dependencies 数组上方的注释禁用
react-hooks/exhausting-deps
单行规则。
useEffect
钩子被传递一个空数组作为第二个参数时,它只在组件挂载时被调用。 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: 'Germany', city: 'Hamburg'}; 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: 'Germany', city: 'Hamburg'}; 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: 'Germany', city: 'Hamburg'}; }, []); 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: 'Germany', 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
挂钩采用内联回调函数和依赖项数组,并返回回调的记忆版本,该回调仅在其中一个依赖项发生更改时才会更改。
或者,禁用 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'); // 👇️ 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> ); }