假设我有3个输入:rate、sendAmount和receiveAmount。我把这3个输入放到useEffect上。规则如下:

如果sendAmount改变了,我计算receiveAmount = sendAmount * rate 如果receiveAmount改变了,我计算sendAmount = receiveAmount / rate 如果速率发生变化,当sendAmount > 0时,我计算receiveAmount = sendAmount * rate;当receiveAmount > 0时,我计算sendAmount = receiveAmount / rate

下面是代码和框https://codesandbox.io/s/pkl6vn7x6j来演示这个问题。

是否有一种方法来比较oldValues和newValues,如componentDidUpdate,而不是为这种情况下做3处理程序?

谢谢


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

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


当前回答

我不喜欢上面的任何答案,我想要传递一个布尔值数组的能力,如果其中一个是真的,那么重新渲染

/**
 * 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]);

其他回答

由于状态与功能组件中的组件实例不是紧密耦合的,因此如果不先保存之前的状态,例如使用useRef,则无法在useEffect中到达之前的状态。这也意味着状态更新可能在错误的位置被错误地实现,因为之前的状态在setState更新函数中可用。

这是useReducer的一个很好的用例,它提供了类似redux的存储,并允许实现各自的模式。状态更新是显式执行的,因此不需要确定更新的是哪个状态属性;这在已调度的行动中已经很清楚了。

下面是一个例子:

function reducer({ sendAmount, receiveAmount, rate }, action) {
  switch (action.type) {
    case "sendAmount":
      sendAmount = action.payload;
      return {
        sendAmount,
        receiveAmount: sendAmount * rate,
        rate
      };
    case "receiveAmount":
      receiveAmount = action.payload;
      return {
        sendAmount: receiveAmount / rate,
        receiveAmount,
        rate
      };
    case "rate":
      rate = action.payload;
      return {
        sendAmount: receiveAmount ? receiveAmount / rate : sendAmount,
        receiveAmount: sendAmount ? sendAmount * rate : receiveAmount,
        rate
      };
    default:
      throw new Error();
  }
}

function handleChange(e) {
  const { name, value } = e.target;
  dispatch({
    type: name,
    payload: value
  });
}

...
const [state, dispatch] = useReducer(reducer, {
  rate: 2,
  sendAmount: 0,
  receiveAmount: 0
});
...

您可以使用useImmer而不是useState来访问状态。 示例:https://css - tricks.com/build -聊天-应用-使用-反应- hook - - code/ - 100 -行-

下面是Aadit M Shah的答案的Typescript版本。

我把它从useTransition重命名为usePrevious,因为useTransition已经存在于React中。

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])

您可以编写一个自定义钩子,使用useRef为您提供先前的道具

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])
}

但是,如果您为每个想要单独处理的变更id分别使用两个useEffect,那么阅读和理解起来会更清晰,可能会更好

使用Ref会在应用程序中引入一种新的错误。

让我们看看这个例子,使用usePrevious,之前有人评论过:

道具。minTime: 5 ==> ref.current = 5 | set ref.current 道具。minTime: 5 ==> ref.current = 5 |的新值等于ref.current 道具。minTime: 8 ==> ref.current = 5 |的新值不等于ref.current 道具。minTime: 5 ==> ref.current = 5 |的新值等于ref.current

正如我们在这里看到的,我们没有更新内部引用,因为我们使用的是useEffect