假设我有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])

我刚刚发布了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解决方案更灵活、干净和简洁。

如果你更喜欢使用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的值将与初始输入值相同。只有当值没有改变时,这才会对您有影响。

由于状态与功能组件中的组件实例不是紧密耦合的,因此如果不先保存之前的状态,例如使用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 -行-