简体   繁体   English

“useEffect 缺少依赖项”警告有时是错误的吗?

[英]Is the "useEffect has a missing dependency" warning sometimes wrong?

I've been using hooks for a while and I never fully understand why React is forcing me to includes some dependencies that I don't want on my useEffect.我已经使用钩子有一段时间了,我从来没有完全理解为什么 React 强迫我在 useEffect 中包含一些我不想要的依赖项。

The way I understand the 'dependencies' of a useEffect hook我理解 useEffect 钩子的“依赖关系”的方式

Add the values you want to 'listen' whenever they change and trigger your effect.添加您想要“聆听”的值,只要它们发生变化并触发您的效果。 This works perfectly with a simple effect like:这与简单的效果完美配合,例如:

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/>
};

However, there are some other cases where is not as straightforward as the example above.但是,还有一些其他情况不像上面的例子那么简单。 In this example we want to trigger the effect only when the id changes:在本例中,我们希望仅在 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/>
};

In this example, I get the warning "React Hook useEffect has a missing dependency" it recommends to include 'callback' on the dependencies array ( option 1 ) or remove the dependencies array ( option 2 ).在此示例中,我收到警告“React Hook useEffect 缺少依赖项”,它建议在依赖项数组(选项 1 )中包含 'callback' 或删除依赖项数组(选项 2 )。

Let's explore the recommendations:让我们来探讨一下建议:

  • Option 1 (Include 'callback' on the dependencies array): Including 'callback' on the dependencies array will cause my effect to trigger whenever the 'id' or the 'callback' changes.选项 1(在依赖项数组中包含 'callback' ):在依赖项数组中包含 'callback' 将导致我的效果在 'id' 或 'callback' 更改时触发。 The problem with this is that I don't want to trigger the effect when the 'callback' changes cause the callback will change in every render.这样做的问题是,当“回调”更改导致回调在每次渲染中都会更改时,我不想触发效果。

  • Option 2 (remove the dependencies array): Removing the dependency array will cause my effect to trigger whenever the component changes which is not the wanted behavior either.选项 2(删除依赖项数组):删除依赖项数组将导致我的效果在组件更改时触发,这也不是想要的行为。

Other options found:发现的其他选项:

I've found some other advice from the community but all of them seem to not accomplish the wanted behavior.我从社区中找到了一些其他建议,但所有这些建议似乎都没有完成想要的行为。 ( https://stackoverflow.com/a/60327893/8168782 ) ( https://stackoverflow.com/a/60327893/8168782 )

Let's quickly review these options:让我们快速回顾一下这些选项:

Option 1: Use an empty dependencies array:选项 1:使用空的依赖项数组:

It will only trigger when the component mounts, not what we want.它只会在组件挂载时触发,而不是我们想要的。

Option 2: Declare the function inside the useEffect()选项 2:在 useEffect() 中声明函数

In this case, the 'callback' is a function passed through props, but either way most of the time you can't declare the function inside the effect because the function is used in other places.在这种情况下,'callback' 是一个通过 props 传递的函数,但无论哪种方式,大多数情况下你都不能在 effect 中声明该函数,因为该函数在其他地方使用。

Option 3: Memoize with useCallback()选项 3:使用 useCallback() 记忆

If you wrap your function inside a useCallback you also need to include the dependencies into the useCallback dependencies array, this will cause the useCallback to trigger again every time the dependencies change so the useEffect also will be triggered.如果您将函数包装在 useCallback 中,您还需要将依赖项包含到 useCallback 依赖项数组中,这将导致每次依赖项更改时再次触发 useCallback,因此也会触发 useEffect。

Option 4: Disable eslint's warnings选项 4:禁用 eslint 的警告

Not considered because I'm trying to understand the problem not simply ignore it.没有考虑,因为我试图理解问题而不是简单地忽略它。

I'm really confused about this warning, I don't know if there are some situations where the warning is wrong and should be ignored (what seems to be wrong) or that I'm missing something.我对这个警告真的很困惑,我不知道是否在某些情况下警告是错误的并且应该被忽略(似乎是错误的)或者我遗漏了什么。

I personally always disable that eslint rule.我个人总是禁用该 eslint 规则。 Due to that eslint has no way to understand your logical intension, it can only exhaustively check all variables captured in the closure and warn you about missing ones from dep-list.由于 eslint 无法理解你的逻辑内涵,它只能彻底检查闭包中捕获的所有变量,并警告你从 dep-list 中丢失的变量。 But a lot of time it's overkilling, just like in your use case.但是很多时候它是矫枉过正的,就像在您的用例中一样。 That's the reasoning behind my choice.这就是我选择的理由。

If you have a clear understanding of how useEffect works, disabling this rule wouldn't cause much pain.如果您对useEffect工作原理有清楚的了解,禁用此规则不会造成太大的痛苦。 I personally don't remember experiencing it.我个人不记得经历过。

A second solution is to leave the rule on, but work around it.第二种解决方案是保留规则,但要解决它。 I got one for you, the useFn custom hook:我为你准备了一个useFn自定义钩子:

function useFn(fn) {
  const ref = useRef(fn);
  ref.current = fn;

  function wrapper() {
    return ref.current.apply(this, arguments)
  }

  return useRef(wrapper).current
}

This hook returns a stable reference of wrapper function, which is just a proxy that call the actual fn , but doesn't change across re-rendering.这个钩子返回一个wrapper函数的稳定引用,它只是一个调用实际fn的代理,但在重新渲染时不会改变。

const SimpleComponent = (props: Props) => {
    const {id, callback: _callback} = props;

    const callback = useFn(_callback)
    
    useEffect(() => {
        callback(id)
    }, [id, callback]);

    return <div/>
};

Now you satisfy that eslint rule meanwhile you don't trigger unwanted useEffect re-run.现在你满足了 eslint 规则,同时你不会触发不需要的useEffect重新运行。


As an off-topic side note.作为题外话。 I also use useFn hook to wrap functions that got passed to child components' props.我还使用useFn hook 来包装传递给子组件的 props 的函数。

Passing arrow function around is heavily used pattern in React.传递箭头函数是 React 中大量使用的模式。 Sometimes you have a component that's expensive to re-render, you React.memo(Component) wrap it, yet then you pass a <Component onClick={e => { ... }} /> inline function, which effectively invalidate the memoize effect.有时你有一个重新渲染很昂贵的组件,你React.memo(Component)包装它,然后你传递一个<Component onClick={e => { ... }} />内联函数,这有效地使记忆效果。 useFn comes to rescue: useFn来拯救:

<Component onClick={useFn(e => { ... })} />

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM