了解 React Hooks 'exhaustive-deps' lint 规则

IT技术 reactjs react-hooks eslint
2021-04-16 01:25:21

我很难理解 'exhaustive-deps' lint 规则。

我已经阅读了这篇文章这篇文章,但我找不到答案。

这是一个带有 lint 问题的简单 React 组件:

const MyCustomComponent = ({onChange}) => {
    const [value, setValue] = useState('');

    useEffect(() => {
        onChange(value);
    }, [value]);

    return (
        <input 
           value={value} 
           type='text' 
           onChange={(event) => setValue(event.target.value)}>
        </input>
    )
} 

它需要我添加onChangeuseEffect依赖项数组。但在我的理解中onChange永远不会改变,所以它不应该存在。

通常我是这样管理的:

const MyCustomComponent = ({onChange}) => {
    const [value, setValue] = useState('');

    const handleChange = (event) => {
        setValue(event.target.value);
        onChange(event.target.value)
    }

    return (
        <input 
           value={value} 
           type='text'
           onChange={handleChange}>
        </input> ​
    )
} 

为什么是棉绒?关于第一个示例的 lint 规则的任何明确解释?

或者我不应该useEffect在这里使用(我是一个有钩子的菜鸟)

2个回答

linter 规则想要onChange进入useEffect钩子的原因是因为可以onChange在渲染之间进行更改,而 lint 规则旨在防止这种“陈旧数据”引用。

例如:

const MyParentComponent = () => {
    const onChange = (value) => { console.log(value); }

    return <MyCustomComponent onChange={onChange} />
}

每个渲染MyParentComponent都会将不同的onChange函数传递MyCustomComponent.

在您的特定情况下,您可能并不关心:您只想onChange 在值更改时调用,而不是在onChange函数更改调用。但是,从您如何使用useEffect.


这里的根源是你useEffect有点单调。

useEffect最好用于副作用,但在这里您将其用作一种“订阅”概念,例如:“当 Y 更改时执行 X”。由于deps数组的机制,这确实在功能上起作用(尽管在这种情况下您还调用onChange了初始渲染,这可能是不需要的),但这不是预期的目的。

调用onChange真的不是一个副作用这里,它只是触发的效果onChange的事件<input>所以,我认为你调用这两个第二个版本onChange,并setValue共同为更地道。

如果有其他设置值的方法(例如清除按钮),不断地记住调用onChange可能会很乏味,所以我可以这样写:

const MyCustomComponent = ({onChange}) => {
    const [value, _setValue] = useState('');

    // Always call onChange when we set the new value
    const setValue = (newVal) => {
        onChange(newVal);
        _setValue(newVal);
    }

    return (
        <input value={value} type='text' onChange={e => setValue(e.target.value)}></input>
        <button onClick={() => setValue("")}>Clear</button>
    )
}

但在这一点上,这是令人毛骨悚然的。

那是一个很好的解释。我也useEffect用作订阅神器。这里唯一让我感到困惑的问题是:如果我还想onChange在组件加载时首先触发怎么办?然后,useEffect[]依赖项有意义但是,皮棉会再次抱怨。
2021-06-12 01:25:21
@zanona 这个useCallback技巧会奏效吗?基本上,您并不是真的想调用onChange,但更具体地说,您想调用在 mount 期间传递的 onChange 函数所以你需要一种方法来记住这个函数是什么,然后调用它。我也尝试添加[!!onChange]为依赖项,但似乎不起作用。
2021-06-17 01:25:21
似乎useCallback也会失败,但useRef更合适:const initialOnChangeRef = useRef(() => onChange(value)); useEffect(() => { initialOnChangeRef.current(); }, [initialOnChangeRef]);.
2021-06-20 01:25:21

exhaustive-deps警告的主要目的是防止开发人员在他们的 effect 中丢失依赖项并丢失一些行为。

Dan abramov - Facebook 核心开发人员 -强烈建议保持该规则启用

对于将函数作为依赖项传递的情况,React FAQ 中有专门的章节

https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies

tl;博士

如果必须在依赖项数组中放置一个函数:

  • 将函数放在组件之外,这样您就可以确保每次渲染时都不会更改引用。
  • 如果可以,请在效果之外调用该函数,并将结果用作依赖项。
  • 如果必须在组件范围内声明函数,则必须使用useCallback钩子记住函数引用仅当回调函数的依赖关系发生变化时,引用才会发生变化。
似乎如果您使用“useCallback”,那么您仍然必须从依赖项中省略记忆化的回调。否则它没有用,因为它严格等同于在效果中定义您的回调并将“useCallback”依赖项移动到“useEffect”依赖项中。它会过于频繁地触发效果。
2021-05-22 01:25:21
@EricBurel 我认为触发器正是 useCallback 正在做的。useEffect 的目的是在依赖项之一发生变化时自动触发回调。
2021-05-27 01:25:21
我觉得useEffect混合了 2 个概念:依赖项,它定义了效果回调(因此当依赖项更改时回调会发生变化)和触发器,它实际上触发了效果回调。对于许多用例,我们可能需要一个带有像useEffect(cb, deps, triggers).
2021-05-28 01:25:21
因此,例如const initialOnChange = useCallback(onChange, []); useEffect(() => initialOnChange(value), [initialOnChange])应该用于触发 onMount 函数吗?编辑:不,它不是:/您需要将 onChange 函数作为 的依赖项传递useCallback,因此它无法按预期工作。
2021-06-05 01:25:21
是的@EricBurel,它与 useEffect 的行为非常相似。如果您从 useCallback 内部调用外部函数,则需要在依赖项数组中声明它。如果直接实现 onChange 而不调用其他函数,它将正常工作。const onChange = useCallback(() => { // implement here }, []);
2021-06-18 01:25:21