【问题标题】:Understanding the React Hooks 'exhaustive-deps' lint rule了解 React Hooks 'exhaustive-deps' lint 规则
【发布时间】:2020-03-11 00:04:27
【问题描述】:

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

我已经阅读了this postthis post 但我找不到答案。

这是一个带有 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>
    )
} 

它需要我将onChange 添加到useEffect 依赖数组中。但据我了解,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 吗? (我是一个有钩子的菜鸟)

【问题讨论】:

  • 是的,这里没有理由使用效果,useEffectcomponentWillMountcomponentDidMount 的组合非常相似,并且当您从useEffect 返回一个函数时,该函数会被处理作为componentWillUnmount。您目前正在处理的只是一个简单的状态更改,useState 钩子足以完成该操作
  • onChange 不会改变,但值会改变。
  • @MikeAbeln 他们不仅在改变状态,而且还在调用作为道具传入的点击处理程序。
  • @DaveNewton 很好,这让我逃脱了。不过,useEffect 似乎并不合适。道具onChange 可以很容易地移动到inputonChange 方法的主体中。虽然为了清楚起见应该重命名。基本上是OP在问题中给出的第二个示例。
  • {onChange} 是对父组件的回调,因此它会在更改时使用输入值进行更新。 (在本例中)

标签: reactjs react-hooks eslint


【解决方案1】:

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 真的不是副作用,它只是为&lt;input&gt; 触发onChange 事件的效果。所以我确实认为你的第二个版本同时调用onChangesetValue 更惯用。

如果有其他设置值的方法(例如清除按钮),经常需要记住调用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[] 依赖关系是否有意义。但随后,lint 又会抱怨。
  • @zanona useCallback 的把戏会奏效吗?基本上,您并不是真的想调用onChange,而是更具体地想调用在挂载期间传递的onChange 函数。所以你需要一种方法来记住这个函数是什么,然后再调用它。我也尝试将[!!onChange] 添加为依赖项,但它似乎不起作用。
  • 看来useCallback也会失败,但useRef更合适:const initialOnChangeRef = useRef(() =&gt; onChange(value)); useEffect(() =&gt; { initialOnChangeRef.current(); }, [initialOnChangeRef]);
【解决方案2】:

exhaustive-deps 警告的主要目的是防止开发人员在其效果中丢失依赖项并丢失某些行为。

Dan abramov – Facebook 核心开发人员 – strongly recommend to keep that rule enabled

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

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

tl;博士

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

  • 将函数放在组件之外,这样您就可以确保每次渲染时都不会更改引用。
  • 如果可以,请在效果之外调用函数,并将结果用作依赖项。
  • 如果必须在组件范围内声明函数,则必须使用useCallback 挂钩来记忆函数引用。仅当回调函数的依赖关系发生变化时,引用才会发生变化。

【讨论】:

  • 那么例如const initialOnChange = useCallback(onChange, []); useEffect(() =&gt; initialOnChange(value), [initialOnChange])应该可以触发onMount函数吗?编辑:不,它没有:/您需要将 onChange 函数作为useCallback 的依赖项传递,因此它不能按预期工作。
  • 是的@EricBurel,它与useEffect 的行为非常相似。如果从 useCallback 内部调用外部函数,则需要在依赖项数组中声明它。如果您直接实现 onChange 而不调用另一个函数,它将正常工作。 const onChange = useCallback(() =&gt; { // implement here }, []);
  • 我觉得useEffect 混合了两个概念:依赖项,定义效果回调(因此当依赖项更改时回调更改),以及触发器,实际触发效果回调。对于许多用例,我们可能需要一个带有类似 useEffect(cb, deps, triggers) 的 API 的钩子。
  • @EricBurel 我认为触发器正是 useCallback 正在做的事情。 useEffect 的目的是在依赖项之一发生变化时自动触发回调。
  • 看起来如果你使用“useCallback”,那么你仍然必须从依赖项中省略记忆回调。否则它就没有用,因为它完全等同于在效果中定义回调并将“useCallback”依赖项移动到“useEffect”依赖项中。它会过于频繁地触发效果。
猜你喜欢
  • 2020-06-09
  • 2021-06-10
  • 2020-05-01
  • 2020-03-18
  • 2022-08-18
  • 2021-04-15
  • 2020-06-08
相关资源
最近更新 更多