【问题标题】:React hook useEffect dependency arrayReact hook useEffect 依赖数组
【发布时间】:2019-08-09 05:15:28
【问题描述】:

我试图围绕 react 的新 hooks api 进行思考。具体来说,我正在尝试构建曾经是以下内容的经典用例:

componentDidUpdate(prevProps) {
    if (prevProps.foo !== this.props.foo) {
        // animate dom elements here...
        this.animateSomething(this.ref, this.props.onAnimationComplete);
    }
}

现在,我尝试使用函数组件和useEffect 构建相同的组件,但不知道该怎么做。这是我尝试过的:

useEffect(() => {
    animateSomething(ref, props.onAnimationComplete);
}, [props.foo]);

这样,只有在 props.foo 发生变化时才会调用效果。这确实有效——但是!这似乎是一种反模式,因为eslint-plugin-react-hooks 将此标记为错误。效果内使用的所有依赖项都应在依赖项数组中声明。这意味着我必须执行以下操作:

useEffect(() => {
    animateSomething(ref, props.onAnimationComplete);
}, [props.foo, ref, props.onAnimationComplete]);

这不会导致 linting 错误,但它完全违背了 props.foo 更改时调用效果的目的。我不想在其他道具或 ref 更改时调用它。

现在,我读到了一些关于使用 useCallback 来包装它的内容。我试过了,但没有进一步。

有人可以帮忙吗?

【问题讨论】:

  • 我猜props.onAnimationComplete变化时不应该触发动画。此外,已经在运行 animateSomething 仍将使用旧回调。

标签: javascript reactjs react-hooks


【解决方案1】:

我建议这样写:

const previousFooRef = useRef(props.foo);

useEffect(() => {
    if (previousFooRef.current !== props.foo) {
       animateSomething(ref, props.onAnimationComplete);
       previousFooRef.current = props.foo;
    }
}, [props.foo, props.onAnimationComplete]);

您无法避免在效果中包含条件的复杂性,因为没有它您将在装载时运行动画,而不仅仅是在props.foo 更改时运行。该条件还允许您在 props.foo 以外的事物发生变化时避免设置动画。

通过在依赖项数组中包含 props.onAnimationComplete,您可以避免禁用 lint 规则,这有助于确保您不会在未来引入与缺少依赖项相关的错误。

这是一个工作示例:

【讨论】:

  • 应该在每次渲染时重新创建回调。对props.onAnimationComplete 的更改应该会触发重新渲染。所以没有真正需要这种复杂性。
  • 你指的是什么复杂度?该条件对于避免在安装时设置动画是必要的。将回调添加到依赖项没有禁用 lint 规则那么复杂。确实,目前在依赖项数组中没有必要,但是随着时间的推移,这段代码可能会以多种方式演变,这很重要,禁用 lint 规则会使未来的错误更有可能发生。
  • 谢谢,这帮助我找到了解决方案!
  • @RyanCogswell 不会抱怨你从 dep 数组中省略了 ref
  • @giorgim linter 知道从useRef does not change across renders 返回的值,所以 refs 不需要在依赖数组中。在示例沙箱中,您可以看到没有 eslint 警告,但如果从依赖数组中删除 foo,您确实会收到警告。
【解决方案2】:

禁止 linter,因为它会给你一个不好的建议。 React 要求您向第二个参数传递(并且只有哪些)更改必须触发效果触发的值。

useEffect(() => {
    animateSomething(ref, props.onAnimationComplete);
}, [props.foo]); // eslint-disable-line react-hooks/exhaustive-deps

它导致与Ryan's solution 相同的结果。

我认为违反这条 linter 规则没有问题。与useCallbackuseMemo 相比,一般情况下不会导致错误。第二个参数的内容是高级逻辑。

您甚至可能想在无关值更改时调用效果:

useEffect(() => {
    alert(`Hi ${props.name}, your score is changed`);
}, [props.score]);

【讨论】:

  • 这个linter规则的原因在你的例子中如下:1)如果animateSomething曾经改变过(即你决定改变动画速度,......),这个效果仍然是指旧功能,您很可能会得到意想不到的结果。 2) 如果name 更改,用户仍会看到旧名称。 - 也许在警报的情况下,您应该返回一个关闭此警报的清理函数,这样您就只会显示相关的警报。
  • 虽然这个例子可能没有必要,但我发现了一些情况,至少对我来说,覆盖 linter 似乎比解决行为更可取,特别是在这种情况下您希望通过传递一个空数组来将效果限制为第一次渲染 - 这仍然会触发穷举警告。
【解决方案3】:

将回调中必须是新的(不是陈旧的)但不能重新触发效果的值移动到 refs:

const elementRef = useRef(); // Ex `ref` from the question
const animationCompleteRef = useRef();

animationCompleteRef.current = props.onAnimationComplete;

useEffect(() => {
    animateSomething(elementRef, animationCompleteRef.current);
}, [props.foo, elementRef, animationCompleteRef]);

之所以有效,是因为 useRef 返回值在渲染时不会改变。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-02-23
    • 2022-06-12
    • 2020-10-26
    • 2020-08-09
    • 2019-10-24
    • 2020-03-07
    • 2020-02-25
    • 2020-06-11
    相关资源
    最近更新 更多