"useEffect has a missing dependency" 警告有时是错误的吗?
Is the "useEffect has a missing dependency" warning sometimes wrong?
我已经使用 hooks 有一段时间了,但我从来没有完全理解为什么 React 会强迫我在 useEffect 中包含一些我不想要的依赖项。
我理解 'dependencies' useEffect 钩子的方式
添加您想要的值 'listen' 每当它们发生变化并触发您的效果时。这与像这样的简单效果完美搭配:
import React, {useEffect, useState} from "react";
interface Props {
id: string
}
const SimpleComponent = (props: Props) => {
const {id} = props;
const [response, setResponse] = useState<object>();
useEffect(() => {
fetch(`https://myexample/${id}`)
.then(response => setResponse(response))
.catch(() => console.log("An error occurs!"))
}, [id])
return <div/>
};
但是,还有一些其他情况不像上面的例子那么简单。本例中我们只想在id改变时触发效果:
import React, {useEffect} from "react";
interface Props {
id: string
callback: Function
}
const SimpleComponent = (props: Props) => {
const {id, callback} = props;
useEffect(() => {
callback(id)
}, [id]);
return <div/>
};
在此示例中,我收到警告“React Hook useEffect 缺少依赖项”,它建议在依赖项数组中包含 'callback'( 选项 1)或删除依赖项数组(选项 2)。
我们来探讨一下建议:
选项 1(在依赖项数组中包含 'callback'):
在 dependencies 数组中包含 'callback' 将导致我的效果在 'id' 或 'callback' 更改时触发。这个问题是我不想在 'callback' 变化时触发效果,导致回调在每次渲染中都会发生变化。
方案二(去掉依赖数组):
删除依赖数组将导致我的效果在组件发生变化时触发,这也不是想要的行为。
找到其他选项:
我从社区找到了一些其他建议,但所有这些建议似乎都没有实现所需的行为。 ()
让我们快速回顾一下这些选项:
选项 1:使用空依赖项数组:
它只会在组件挂载时触发,而不是我们想要的。
选项2:在useEffect()
中声明函数
在这种情况下,'callback' 是一个通过 props 传递的函数,但无论哪种方式,大多数时候您都不能在效果中声明该函数,因为该函数在其他地方使用。
选项 3:使用 useCallback() 进行记忆
如果将函数包装在 useCallback 中,则还需要将依赖项包含到 useCallback 依赖项数组中,这将导致每次依赖项更改时 useCallback 再次触发,因此 useEffect 也会被触发。
选项 4:禁用 eslint 的警告
未考虑,因为我试图理解问题而不是简单地忽略它。
我真的对这个警告感到困惑,我不知道是否有一些情况警告是错误的并且应该被忽略(似乎是错误的)或者我遗漏了什么。
我个人总是禁用那个 eslint 规则。由于 eslint 无法理解你的逻辑意图,它只能详尽地检查闭包中捕获的所有变量,并警告你丢失 dep-list 中的变量。但是很多时候它是矫枉过正的,就像在您的用例中一样。这就是我选择背后的原因。
如果您清楚地了解 useEffect
的工作原理,禁用此规则不会造成太大的痛苦。我个人不记得经历过。
第二种解决方案是保留规则,但变通解决。我为你准备了一个,useFn
自定义挂钩:
function useFn(fn) {
const ref = useRef(fn);
ref.current = fn;
function wrapper() {
return ref.current.apply(this, arguments)
}
return useRef(wrapper).current
}
此挂钩 returns 是 wrapper
函数的稳定引用,它只是调用实际 fn
的代理,但不会在 re-rendering 中发生变化。
const SimpleComponent = (props: Props) => {
const {id, callback: _callback} = props;
const callback = useFn(_callback)
useEffect(() => {
callback(id)
}, [id, callback]);
return <div/>
};
现在您满足了 eslint 规则,同时您不会触发不需要的 useEffect
re-run.
作为 off-topic 旁注。我还使用 useFn
钩子来包装传递给子组件道具的函数。
传递箭头函数是 React 中大量使用的模式。有时你有一个组件 re-render,你 React.memo(Component)
包装它,然后你传递一个 <Component onClick={e => { ... }} />
内联函数,这有效地使 memoize 效果无效。 useFn
前来救援:
<Component onClick={useFn(e => { ... })} />
我已经使用 hooks 有一段时间了,但我从来没有完全理解为什么 React 会强迫我在 useEffect 中包含一些我不想要的依赖项。
我理解 'dependencies' useEffect 钩子的方式
添加您想要的值 'listen' 每当它们发生变化并触发您的效果时。这与像这样的简单效果完美搭配:
import React, {useEffect, useState} from "react";
interface Props {
id: string
}
const SimpleComponent = (props: Props) => {
const {id} = props;
const [response, setResponse] = useState<object>();
useEffect(() => {
fetch(`https://myexample/${id}`)
.then(response => setResponse(response))
.catch(() => console.log("An error occurs!"))
}, [id])
return <div/>
};
但是,还有一些其他情况不像上面的例子那么简单。本例中我们只想在id改变时触发效果:
import React, {useEffect} from "react";
interface Props {
id: string
callback: Function
}
const SimpleComponent = (props: Props) => {
const {id, callback} = props;
useEffect(() => {
callback(id)
}, [id]);
return <div/>
};
在此示例中,我收到警告“React Hook useEffect 缺少依赖项”,它建议在依赖项数组中包含 'callback'( 选项 1)或删除依赖项数组(选项 2)。
我们来探讨一下建议:
选项 1(在依赖项数组中包含 'callback'): 在 dependencies 数组中包含 'callback' 将导致我的效果在 'id' 或 'callback' 更改时触发。这个问题是我不想在 'callback' 变化时触发效果,导致回调在每次渲染中都会发生变化。
方案二(去掉依赖数组): 删除依赖数组将导致我的效果在组件发生变化时触发,这也不是想要的行为。
找到其他选项:
我从社区找到了一些其他建议,但所有这些建议似乎都没有实现所需的行为。 (
让我们快速回顾一下这些选项:
选项 1:使用空依赖项数组:
它只会在组件挂载时触发,而不是我们想要的。
选项2:在useEffect()
中声明函数在这种情况下,'callback' 是一个通过 props 传递的函数,但无论哪种方式,大多数时候您都不能在效果中声明该函数,因为该函数在其他地方使用。
选项 3:使用 useCallback() 进行记忆
如果将函数包装在 useCallback 中,则还需要将依赖项包含到 useCallback 依赖项数组中,这将导致每次依赖项更改时 useCallback 再次触发,因此 useEffect 也会被触发。
选项 4:禁用 eslint 的警告
未考虑,因为我试图理解问题而不是简单地忽略它。
我真的对这个警告感到困惑,我不知道是否有一些情况警告是错误的并且应该被忽略(似乎是错误的)或者我遗漏了什么。
我个人总是禁用那个 eslint 规则。由于 eslint 无法理解你的逻辑意图,它只能详尽地检查闭包中捕获的所有变量,并警告你丢失 dep-list 中的变量。但是很多时候它是矫枉过正的,就像在您的用例中一样。这就是我选择背后的原因。
如果您清楚地了解 useEffect
的工作原理,禁用此规则不会造成太大的痛苦。我个人不记得经历过。
第二种解决方案是保留规则,但变通解决。我为你准备了一个,useFn
自定义挂钩:
function useFn(fn) {
const ref = useRef(fn);
ref.current = fn;
function wrapper() {
return ref.current.apply(this, arguments)
}
return useRef(wrapper).current
}
此挂钩 returns 是 wrapper
函数的稳定引用,它只是调用实际 fn
的代理,但不会在 re-rendering 中发生变化。
const SimpleComponent = (props: Props) => {
const {id, callback: _callback} = props;
const callback = useFn(_callback)
useEffect(() => {
callback(id)
}, [id, callback]);
return <div/>
};
现在您满足了 eslint 规则,同时您不会触发不需要的 useEffect
re-run.
作为 off-topic 旁注。我还使用 useFn
钩子来包装传递给子组件道具的函数。
传递箭头函数是 React 中大量使用的模式。有时你有一个组件 re-render,你 React.memo(Component)
包装它,然后你传递一个 <Component onClick={e => { ... }} />
内联函数,这有效地使 memoize 效果无效。 useFn
前来救援:
<Component onClick={useFn(e => { ... })} />