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

rwi*_*ang 77 reactjs react-hooks

假设我有3个输入:rate,sendAmount和receiveAmount.我把3个输入放在useEffect diffing params上.规则是:

  • 如果sendAmount发生了变化,我会计算出来 receiveAmount = sendAmount * rate
  • 如果receiveAmount发生了变化,我会计算出来 sendAmount = receiveAmount / rate
  • 如果费率改变,我计算receiveAmount = sendAmount * rate何时sendAmount > 0或我计算sendAmount = receiveAmount / rate何时receiveAmount > 0

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

有没有办法比较oldValuesnewValues喜欢,componentDidUpdate而不是为这种情况下制作3个处理程序?

谢谢


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

在这种情况下,我不能使用多个useEffect因为每个更改导致相同的网络调用.这也是我也用它changeCount来跟踪变化的原因.这changeCount也有助于跟踪仅来自本地的更改,因此我可以防止因服务器更改而进行不必要的网络调用.

Shu*_*tri 107

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

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}
Run Code Online (Sandbox Code Playgroud)

然后使用它 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])
}
Run Code Online (Sandbox Code Playgroud)

然而,如果您useEffect为每个更改ID单独使用两个,则它更清晰,可能更好,更清晰,您可以单独处理它们

  • `react` 变得越来越糟糕:人们使用 `useRef` 只是为了接收以前的 props(!),而不关心它会花费多少钱。 (13认同)
  • 在`usePrevious`中,`useEffect`不应该依赖于`value`吗?否则,如果组件由于不同的状态更改而重新渲染,那么在下一次渲染中,“previousValue”将等于“value”,对吗?或者我错过了什么? (9认同)
  • 需要有人让我相信这个编程模型比 prevProps 和 prevState 更好。 (5认同)
  • 我尝试了上面的代码,但是 eslint 警告我 `useEffect` 缺少依赖项 `prevAmount` (4认同)
  • 感谢您提供有关分别使用两个`useEffect`调用的说明。不知道您可以多次使用它! (3认同)
  • 由于 prevAmount 保存 state/props 的先前值的值,因此您无需将其作为依赖项传递给 useEffect 并且您可以针对特定情况禁用此警告。你可以阅读这篇文章了解更多详情? (3认同)

Dra*_*vuk 18

离开接受的答案,一个不需要自定义钩子的替代解决方案:

const Component = ({ receiveAmount, sendAmount }) => {
  const prevAmount = useRef({ receiveAmount, sendAmount }).current;
  useEffect(() => {
    if (prevAmount.receiveAmount !== receiveAmount) {
     // process here
    }
    if (prevAmount.sendAmount !== sendAmount) {
     // process here
    }
    return () => { 
      prevAmount.receiveAmount = receiveAmount;
      prevAmount.sendAmount = sendAmount;
    };
  }, [receiveAmount, sendAmount]);
};
Run Code Online (Sandbox Code Playgroud)

这假设您实际上需要引用“此处处理”位中任何内容的先前值。否则,除非您的条件超出了直接!==比较的范围,否则这里最简单的解决方案就是:

const Component = ({ receiveAmount, sendAmount }) => {
  useEffect(() => {
     // process here
  }, [receiveAmount]);

  useEffect(() => {
     // process here
  }, [sendAmount]);
};
Run Code Online (Sandbox Code Playgroud)

  • 这太酷了,如果我能投票更多的话我会的!也完全有道理,我最初的答案来自一个无知的地方。我认为这可能是迄今为止我见过的最简单、最优雅的模式。 (2认同)

See*_*ROM 13

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

import { useEffect, useRef } from "react";

const usePrevious = <T extends {}>(value: T) => {
  const ref = useRef<T>();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};
Run Code Online (Sandbox Code Playgroud)

  • 要使其在 TSX 文件中工作,您还可以编写 &lt;T,&gt; 以区别于 JSX 语法(重点关注尾随逗号) (3认同)
  • 请注意,这在TSX文件中不起作用,要使其在TSX文件中起作用,将其更改为const usePrevious = &lt;T扩展any&gt;(......),以使解释器认为&lt;T&gt;不是JSX,并且是通用限制 (2认同)
  • 你能解释一下为什么 `&lt;T extends {}&gt;` 不起作用吗?它似乎对我有用,但我试图理解这样使用它的复杂性。 (2认同)
  • @fgblomqvist 更新了我的答案。再次感谢您的反馈。 (2认同)

Ben*_*arp 12

选项1-useCompare挂钩

将当前值与先前值进行比较是一种常见的模式,它证明了自己的自定义钩子是正确的,从而隐藏了实现细节。

const useCompare = (val: any) => {
    const prevVal = usePrevious(val)
    return prevVal !== val
}

const usePrevious = (value) {
    const ref = useRef();
    useEffect(() => {
      ref.current = value;
    });
    return ref.current;
}

const Component = (props) => {
  ...
  const hasVal1Changed = useCompare(val1)
  const hasVal2Changed = useCompare(val2);
  useEffect(() => {
    if (hasVal1Changed ) {
      console.log("val1 has changed");
    }
    if (hasVal2Changed ) {
      console.log("val2 has changed");
    }
  });

  return <div>...</div>;
};
Run Code Online (Sandbox Code Playgroud)

演示版

选项2-值更改时运行useEffect

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

  return <div>...</div>;
};
Run Code Online (Sandbox Code Playgroud)

演示版


Aus*_*rba 7

我刚刚发布了react-delta解决了这种确切的场景。在我看来,useEffect有太多的责任。

职责

  1. 它使用以下方法比较其依赖项数组中的所有值 Object.is
  2. 它根据 #1 的结果运行效果/清理回调

分解责任

react-deltauseEffect的职责分解为几个较小的钩子。

责任#1

责任#2

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


Aad*_*hah 7

这是我使用的一个自定义钩子,我认为它比使用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)
}
Run Code Online (Sandbox Code Playgroud)

您可以按如下方式使用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])
Run Code Online (Sandbox Code Playgroud)

希望有帮助。


Est*_*ask 6

由于状态与功能组件中的组件实例并不紧密耦合,因此如果不先保存之前的状态(useEffect例如,使用useRef. 这也意味着状态更新可能在错误的位置错误地实现,因为先前的状态在更新器函数中可用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
});
...
Run Code Online (Sandbox Code Playgroud)


Joe*_*wen 5

如果您更喜欢useEffect替换方法:

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

  useEffect(() => {
    fn(previousInputsRef.current)
    previousInputsRef.current = [...inputs]
  }, inputs)
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

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

请注意,一次执行效果时,传递给您的先前值fn将与您的初始输入值相同。这只会事给你,如果你想要做的东西,当值没有改变。