假设我有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还有助于仅从本地跟踪更改,因此可以防止由于服务器更改而引起的不必要的网络调用。


当前回答

我刚刚发布了react-delta,它解决了这种情况。在我看来,useEffect有太多的责任。

责任

它使用Object.is比较其依赖数组中的所有值 它基于#1的结果运行effect/cleanup回调

分解责任

react-delta将useEffect的职责分解为几个更小的钩子。

责任# 1

usePrevious(值) useLatest(值) useDelta(价值,选项) useDeltaArray (valueArray选项) useDeltaObject (valueObject选项) 一些(deltaArray) 每一个(deltaArray)

责任# 2

布尔useConditionalEffect(回调)

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

其他回答

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

在.tsx模块中:

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;
};

或者在.ts模块中:

import { useEffect, useRef } from "react";

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

如果你更喜欢使用useEffect替换方法:

const usePreviousEffect = (fn, inputs = []) => {
  const previousInputsRef = useRef([...inputs])

  useEffect(() => {
    fn(previousInputsRef.current)
    previousInputsRef.current = [...inputs]
  }, inputs)
}

像这样使用它:

usePreviousEffect(
  ([prevReceiveAmount, prevSendAmount]) => {
    if (prevReceiveAmount !== receiveAmount) // side effect here
    if (prevSendAmount !== sendAmount) // side effect here
  },
  [receiveAmount, sendAmount]
)

注意,第一次执行效果时,先前传递给fn的值将与初始输入值相同。只有当值没有改变时,这才会对您有影响。

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

const Component = (props) => {

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

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

Demo

选项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;
}


Demo

下面是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])

在你的例子中(简单对象):

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

在其他情况下(复杂对象)

const {cityInfo} = props;
useEffect(()=>{
  // some logic
}, [cityInfo.cityId])