如何在 React Hooks useEffect 上比较 oldValues 和 newValues?

IT技术 reactjs react-hooks
2021-04-20 23:56:08

假设我有 3 个输入:rate、sendAmount 和 receiveAmount。我把这 3 个输入放在 useEffect diffing 参数上。规则是:

  • 如果 sendAmount 改变,我计算 receiveAmount = sendAmount * rate
  • 如果 receiveAmount 改变,我计算 sendAmount = receiveAmount / rate
  • 如果汇率改变,我计算receiveAmount = sendAmount * rate何时sendAmount > 0或我计算sendAmount = receiveAmount / rate何时receiveAmount > 0

这是用于演示问题的代码和框https://codesandbox.io/s/pkl6vn7x6j

有没有办法比较oldValuesand newValueslike oncomponentDidUpdate而不是为这种情况制作 3 个处理程序?

谢谢


这是我使用https://codesandbox.io/s/30n01w2r06 的最终解决方案usePrevious

在这种情况下,我不能使用多个,useEffect因为每次更改都会导致相同的网络调用。这就是为什么我也使用changeCount跟踪更改的原因changeCount也有助于仅从本地跟踪更改,因此我可以防止由于来自服务器的更改而导致不必要的网络调用。

6个回答

您可以编写一个自定义钩子来使用useRef为您提供以前的props

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

然后在 useEffect

const Component = (props) => {
    const {receiveAmount, sendAmount } = props
    const prevAmount = usePrevious({receiveAmount, sendAmount});
    useEffect(() => {
        if(prevAmount.receiveAmount !== receiveAmount) {

         // process here
        }
        if(prevAmount.sendAmount !== sendAmount) {

         // process here
        }
    }, [receiveAmount, sendAmount])
}

但是,如果您useEffect为要单独处理它们的每个更改 id 分别使用两个,它会更清晰,可能更好更清晰地阅读和理解

我尝试了上面的代码,但是 eslint 警告我useEffect缺少依赖项prevAmount
2021-05-25 23:56:08
喜欢这个 - useRef 总是能派上用场。
2021-06-01 23:56:08
由于 prevAmount 保存 state/props 的先前值的值,因此您无需将其作为依赖项传递给 useEffect 并且您可以针对特定情况禁用此警告。你可以阅读这篇文章了解更多详情?
2021-06-05 23:56:08
感谢您关于分别使用两个useEffect调用的说明不知道你可以多次使用它!
2021-06-11 23:56:08
在 中usePrevious,不应该useEffect依赖于value吗?否则,如果组件由于不同的状态更改而重新渲染,在下一次渲染中,previousValue将等于value,对吗?或者我错过了什么?
2021-06-17 23:56:08

如果有人正在寻找 usePrevious 的 TypeScript 版本:

在一个.tsxmodule中:

import { useEffect, useRef } from "react";

const usePrevious = <T extends unknown>(value: T): T | undefined => {
  const ref = useRef<T>();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};

或者在一个.tsmodule中:

import { useEffect, useRef } from "react";

const usePrevious = <T>(value: T): T | undefined => {
  const ref = useRef<T>();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};
请注意,这在 TSX 文件中不起作用,const usePrevious = <T extends any>(...要使其在TSX 文件中工作,请更改以使解释器认为 <T> 不是 JSX 并且是通用限制
2021-05-21 23:56:08
你能解释为什么<T extends {}>不起作用。它似乎对我有用,但我试图了解像这样使用它的复杂性。
2021-05-22 23:56:08
最好扩展未知,或者简单地将钩子放在.ts文件中。如果您扩展{},如果您跳过指定 T in ,则会出现错误usePrevious<T>
2021-06-08 23:56:08
@fgblomqvist 更新了我的答案。再次感谢您的反馈。
2021-06-10 23:56:08
要使其在 TSX 文件中工作,您还可以编写 <T,> 以区别于 JSX 语法(重点放在尾随逗号)
2021-06-13 23:56:08

选项 1 - 当值改变时运行 useEffect

const Component = (props) => {

  useEffect(() => {
    console.log("val1 has changed");
  }, [val1]);

  return <div>...</div>;
};

演示

选项 2 - useHasChanged 钩子

将当前值与先前值进行比较是一种常见模式,并且证明了它自己的隐藏实现细节的自定义钩子是合理的。

const Component = (props) => {
  const hasVal1Changed = useHasChanged(val1)

  useEffect(() => {
    if (hasVal1Changed ) {
      console.log("val1 has changed");
    }
  });

  return <div>...</div>;
};

const useHasChanged= (val: any) => {
    const prevVal = usePrevious(val)
    return prevVal !== val
}

const usePrevious = (value) => {
    const ref = useRef();
    useEffect(() => {
      ref.current = value;
    });
    return ref.current;
}


演示

第二种选择对我有用。你能指导我为什么我必须写 useEffect Twice 吗?
2021-05-30 23:56:08
谢谢回复。当你说“在我们传递的函数内部,我们必须弄清楚哪个 val 发生了变化”。我们如何实现它。请指导
2021-06-09 23:56:08
谢谢,选项 2 - 正是我需要的
2021-06-09 23:56:08
@TarunNagpal,您不一定需要使用 useEffect 两次。这取决于您的用例。想象一下,我们只想记录哪个 val 发生了变化。如果我们的依赖项数组中同时具有 val1 和 val2,则每次任何值发生更改时它都会运行。然后在我们传递的函数中,我们必须找出哪个 val 已更改以记录正确的消息。
2021-06-16 23:56:08

离开接受的答案,一个不需要自定义钩子的替代解决方案:

const Component = ({ receiveAmount, sendAmount }) => {
  const prevAmount = useRef({ receiveAmount, sendAmount }).current;
  useEffect(() => {
    if (prevAmount.receiveAmount !== receiveAmount) {
     // process here
    }
    if (prevAmount.sendAmount !== sendAmount) {
     // process here
    }
    return () => { 
      prevAmount.receiveAmount = receiveAmount;
      prevAmount.sendAmount = sendAmount;
    };
  }, [receiveAmount, sendAmount]);
};

这假设您实际上需要参考“此处处理”位中的任何内容的先前值。否则,除非您的条件超出了直接!==比较的范围,否则这里最简单的解决方案就是:

const Component = ({ receiveAmount, sendAmount }) => {
  useEffect(() => {
     // process here
  }, [receiveAmount]);

  useEffect(() => {
     // process here
  }, [sendAmount]);
};
这将为您提供初始值而不是以前的值。usePrevious()钩子起作用的原因是因为它被包裹在useEffect()
2021-05-21 23:56:08
这太酷了,如果我能投票更多,我会的!完全有道理,我最初的答案来自一个无知的地方。我认为这可能是迄今为止我见过的最简单、最优雅的模式。
2021-05-27 23:56:08
您可能可以进一步抽象它以制作一个超级简单的实用程序。
2021-05-29 23:56:08
很好的解决方案,但包含语法错误。 useRef不是userRef忘记使用当前prevAmount.current
2021-06-05 23:56:08
@Justin 返回函数应该在每次渲染时更新以前的值 ref。你测试过吗?
2021-06-12 23:56:08

我刚刚发布了react-delta解决了这种确切的场景。在我看来,useEffect有太多的责任。

职责

  1. 它使用以下方法比较其依赖项数组中的所有值 Object.is
  2. 它根据 #1 的结果运行效果/清理回调

分解责任

react-deltauseEffect的职责分解为几个较小的钩子。

责任#1

责任#2

根据我的经验,这种方法比useEffect/useRef解决方案更灵活、更干净、更简洁

正是我要找的。要去试试看!
2021-05-25 23:56:08
@Hisato 很可能是矫枉过正。这是一个实验性的 API。坦率地说,我在我的团队中并没有经常使用它,因为它没有广为人知或被采用。理论上这听起来不错,但在实践中可能不值得。
2021-06-02 23:56:08