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

391
假设我有3个输入:汇率、发送金额和接收金额。我将这3个输入放在useEffect差分参数上。规则如下:
- 如果发送金额发生变化,则计算 `receiveAmount = sendAmount * rate` - 如果接收金额发生变化,则计算 `sendAmount = receiveAmount / rate` - 如果汇率发生变化,则当 `sendAmount > 0` 时计算 `receiveAmount = sendAmount * rate` 或者当 `receiveAmount > 0` 时计算 `sendAmount = receiveAmount / rate`
这是codesandbox https://codesandbox.io/s/pkl6vn7x6j 来演示问题。
是否有一种方法可以像在componentDidUpdate中一样比较oldValues和newValues,而不是为此情况制作3个处理程序?
谢谢

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

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


componentDidUpdate 究竟如何帮助呢?你仍然需要编写这三个条件。 - Estus Flask
我添加了一个答案,其中包含2个可选解决方案。其中一个适用于您吗? - Ben Carp
17个回答

3
使用Ref将会在应用中引入一种新的错误。
让我们使用之前有人提到的usePrevious来看一下这个案例:
  1. prop.minTime: 5 ==> ref.current = 5 | 设置ref.current
  2. prop.minTime: 5 ==> ref.current = 5 | 新值等于ref.current
  3. prop.minTime: 8 ==> ref.current = 5 | 新值不等于ref.current
  4. prop.minTime: 5 ==> ref.current = 5 | 新值等于ref.current
正如我们在这里看到的,我们没有更新内部的ref,因为我们使用了useEffect

1
React有这个例子,但是他们在使用state而不是props,当你关心旧的props时,将会发生错误。 - santomegonzalo

0

这里有一个 TypeScript 版本 Aadit M Shah's Answer

我将其从 useTransition 重命名为 usePrevious,因为在 React 中已经存在 useTransition

import { useEffect, useRef, useState } from 'react';

const usePrevious = <T extends any[],>(callback: (prev: T) => void, deps: T): void => {
  const callbackRef = useRef<null | ((prev: T) => void)>(null);

  useEffect(() => {
    callbackRef.current = callback;
  }, [callback]);

  const depsRef = useRef<null | T>(null);

  const [initial, setInitial] = useState(true);

  useEffect(() => {
    if (!initial && depsRef.current !== null && callbackRef.current !== null) {
      callbackRef.current(depsRef.current);
    }

    depsRef.current = deps;
    setInitial(false);
  }, deps);
}

export default usePrevious;

使用方法:

  usePrevious<[boolean]>(([prevIsOpen]) => {
    console.log('prev', prevIsOpen);
    console.log('now', isOpen);
  }, [isOpen])

你不觉得这很复杂吗? - Azhar Uddin Sheikh

0

关于已接受的答案似乎存在问题。

已接受答案的问题:

如果您的组件父组件重新渲染,但值仍然相同,usePrevious钩子将显示新值。

我的解决方案

这样修复了问题,使得在父组件重新渲染时,先前的值仍然存在。

export const usePrevious = <T>(value: T) => {
const previousValue = useRef<T>();
const newValue = useRef<T>();

if (newValue.current !== value) {
    previousValue.current = newValue.current;
    newValue.current = value;
}

return previousValue.current;

};

-1

-1

我不喜欢上面的任何答案,我想要能够传递一个布尔数组,如果其中一个为真,则重新渲染。

/**
 * effect fires if one of the conditions in the dependency array is true
 */
export const useEffectCompare = (callback: () => void, conditions: boolean[], effect = useEffect) => {
  const shouldUpdate = useRef(false);
  if (conditions.some((cond) => cond)) shouldUpdate.current = !shouldUpdate.current;
  effect(callback, [shouldUpdate.current]);
};

//usage - will fire because one of the dependencies is true.
useEffectCompare(() => {
  console.log('test!');
}, [false, true]);

-1

这是我在Typescript中的实现方式:

import { useEffect, useRef } from "react";

export default function usePrevious<T extends any[]>(fn: (prev: T) => any, deps: T) {
  const previousDepsRef = useRef<T>(deps);

  useEffect(() => {
    const eject = fn(previousDepsRef.current);
    previousDepsRef.current = deps;
    return eject;
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, deps);
}

然后像这样使用它:

usePrevious(([prevCount]) => {
  console.log(prevCount, count);
}, [count]);

-2

在您的情况下(简单对象):

useEffect(()=>{
  // your logic
}, [rate, sendAmount, receiveAmount])

在另一种情况(复杂对象)中
const {cityInfo} = props;
useEffect(()=>{
  // some logic
}, [cityInfo.cityId])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接