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


当前回答

小心大多数投票的答案。对于上述更复杂的场景,usePrevious的变化可能会给你太多的重渲染(1)或与original(2)相同的值。

我们必须:

在useEffect中添加[value]作为依赖项,仅当值发生变化时重新运行 将JSON.parse(JSON.stringify(value))(或一些深度拷贝)赋值给useEffect中的ref.current,以防止将state的引用传递给ref而不是值

升级看点:

const usePrevious = <T>(value: T): T => {
  const ref: any = useRef<T>()

  useEffect(() => {
    ref.current = JSON.parse(JSON.stringify(value))
  }, [value])

  return ref.current
}

其他回答

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

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

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

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

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

下面是我使用的一个自定义钩子,我认为它比使用usePrevious更直观。

import { useRef, useEffect } from 'react'

// useTransition :: Array a => (a -> Void, a) -> Void
//                              |_______|  |
//                                  |      |
//                              callback  deps
//
// The useTransition hook is similar to the useEffect hook. It requires
// a callback function and an array of dependencies. Unlike the useEffect
// hook, the callback function is only called when the dependencies change.
// Hence, it's not called when the component mounts because there is no change
// in the dependencies. The callback function is supplied the previous array of
// dependencies which it can use to perform transition-based effects.
const useTransition = (callback, deps) => {
  const func = useRef(null)

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

  const args = useRef(null)

  useEffect(() => {
    if (args.current !== null) func.current(...args.current)
    args.current = deps
  }, deps)
}

您可以像下面那样使用useTransition。

useTransition((prevRate, prevSendAmount, prevReceiveAmount) => {
  if (sendAmount !== prevSendAmount || rate !== prevRate && sendAmount > 0) {
    const newReceiveAmount = sendAmount * rate
    // do something
  } else {
    const newSendAmount = receiveAmount / rate
    // do something
  }
}, [rate, sendAmount, receiveAmount])

希望这能有所帮助。

对于真正简单的道具比较,您可以使用useEffect轻松查看道具是否已更新。

const myComponent = ({ prop }) => {
  useEffect(() => {
    ---Do stuffhere----
  }, [prop])
}

useEffect只在道具改变时才会运行代码。

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