如何比较 React Hooks useEffect 上的 oldValues 和 newValues?多次重新渲染

Posted

技术标签:

【中文标题】如何比较 React Hooks useEffect 上的 oldValues 和 newValues?多次重新渲染【英文标题】:How to compare oldValues and newValues on React Hooks useEffect? Multiple re-renders 【发布时间】:2021-11-13 05:29:30 【问题描述】:

有点像这里描述的问题 How to compare oldValues and newValues on React Hooks useEffect?

但就我而言,usePrevious 钩子没有帮助。

想象一下具有多个输入、选择等的表单。您可能想查看https://app.uniswap.org/#/swap 以进行类似的可视化。几乎任何更改都会发生一些操作和数据更新,这将导致多次重新渲染,至少 4 次。例如。

我有 2 个输入,每个代表一个令牌。 Base(第一个)和 Quote(第二个)。

这是 Base 的状态

const [base, setBase] = useState(
    balance: undefined,
    price: undefined,
    value: initState?.base?.value,
    token: initState?.base?.token,
    tokenId: initState?.base?.tokenId,
);

为了报价

const [quote, setQuote] = useState(
    balance: undefined,
    price: undefined,
    value: initState?.quote?.value,
    token: initState?.quote?.token,
    tokenId: initState?.quote?.tokenId,
);

它们会形成一对,例如 BTC/USD

通过在选择菜单中更改token(而不是BTC,我将选择ETH)我将触发几个操作:获取钱包余额,获取价格,然后将通过输入视图更新和模式窗口关闭进行更多的重新渲染。因此,其中至少有 4 个正在发生。我希望能够将base.tokenbasePrvconst basePrv = usePrevious(base?.token); 但在第二次重新渲染时,base.tokenbasePrv 已经拥有相同的令牌属性,这是一个问题。

我也有输入之间的交换功能,我应该用引号改变基数和像这样用基数改变引号

setBase(prevState => (
    ...prevState,
    base: quote
));

setQuote(prevState => (
    ...prevState,
    quote: base
));

在这种情况下,没有应触发的其他请求。

现在我有 useEffecttoken 依赖于它。但是每次更改令牌时都会触发它,如果您要快速单击,这将导致额外的异步调用和请求的“尾部”。这就是为什么我需要比较更改之前的 token 属性以了解我是否应该因为新货币对的形成而发出额外的调用和请求(BTC/USD 变为 ETH/USD )或者我应该忽略它,因为它只是一个“掉期”(BTC/USD 变成 USD/BTC)并且没有必要进行额外的调用并获取。我只需要,嗯,交换它们,而不是更多。

所以在我的故事中,usePrevious 钩子只会返回一次之前的令牌属性,而在第二次和第三次时,它将被多次重新渲染(将获取其他属性)覆盖到新的。所以在useEffect被触发的时候,我没有机会比较之前的token属性和现在的token属性,因为它们会显示相同的。

我对如何解决它有几个想法,但我不确定它是对还是错,因为在我看来,这些决定看起来比声明性更重要。

    我可以保持一切原样(无论是什么更改,请求都会在任何更改时触发。是交换还是用户更改了一对)。我可以禁用交换按钮,直到所有请求都完成。它将解决请求“尾巴”的问题。但这是一个修补程序,它会起作用,但我不喜欢它,因为它会导致额外的不必要的请求,而且它会很慢而且对 UX 不利。

    setBasesetQuote 发生更新之前,我可以使用一个状态来保留前一对。它将允许我使用 useEffect 并将前一对与当前一对进行比较,以了解这对是否已更改,或者只是交换并决定是否进行获取和调用。

    我可以摆脱 useEffectbase.tokenquote.token 依赖关系并处理 onClick 处理程序内部的所有内容。因此,交换功能不会触发useEffect,并且只有当用户单击并选择不同的东西时才会触发调用和提取。但正如我所说,这个选项对我来说似乎有点奇怪。

    我在这里尝试使用闭包来“记住”令牌的先前状态,但这有点类似于使用当前组件状态。此外,您必须在功能组件主体之外初始化闭包,而且我看不到以这种方式将 init 状态转移到其中的可能性,因此代码变得更加意大利面条。

还有其他想法吗?我肯定错过了一些东西。也许这么多重新渲染是一种反模式,但我不知道如何避免这种情况。

【问题讨论】:

为什么不创建一个 prevBase 状态并在可以 setBase 时更新它? 这是我在第二种情况下描述的决定。是的,这是可能的,但我选择了第 3 个选项,因为这样我不需要在令牌交换上添加额外的条件或状态或补丁。通过使用useEffect,它应该使用一些条件来检查和确定我是否应该拨打电话。所以我把所有东西都留在了点击处理程序上。我只是不确定这是否是一个好习惯。 我正在回答,因为我无法在评论中给出代码示例。让我知道它是否提供了解决方案。 . . 【参考方案1】:

您的问题可能有多种解决方案。我建议选择一个更容易理解的。

1。修改usePrevious钩子

您可以修改 usePrevious 挂钩以在多次渲染中存活。

提示:使用JSON.stringify 比较您是否认为该值将是一个复杂对象,并且即使对于相同的实际值也可能会更改引用。

function usePrevious(value) 
  const prevRef = useRef();
  const curRef = useRef();

  if (value !== curRef.current)
  // or, use
  // if ( JSON.stringify(value) !== JSON.stringify(curRef.current))
    prevRef.current = curRef.current;
    curRef.current = value;
  
  
  return prevRef.current;

2。排序useEffect依赖数组

由于您使用标记(字符串)作为useEffect 的依赖数组,并且您不介意它们的顺序(交换不应该改变任何东西),因此对依赖数组进行排序

useEffect(
  () => 
    // do some effect
  ,
  [base.token, quote.token].sort()
)

3。存储当前获取的令牌。

在存储 API 响应数据的同时,还存储与该数据关联的令牌(请求的一部分)。现在,您将拥有 2 组令牌。

    当前选择的令牌 当前提取的令牌

您可以选择仅在当前提取的令牌不能满足您的需求时提取。您还可以扩展它并存储以前的 API 请求/响应,并尽可能从中选择结果。

判决

在所有这些中,3rd 对我来说似乎是一个不错且更标准化的方法,但对您的需求来说有点过头了(除非您想缓存以前的结果)。

由于简单和极简主义,我会选择 2nd。但是,这仍然取决于您最终会发现什么更容易。

【讨论】:

谢谢!第二个好像不错。实际上,它们不是字符串,而是对象,但可以将它们的符号作为依赖项。我只是对在所选令牌项目的点击处理程序中有这么多东西感到困惑。但最终,更好的解决方案是可以轻松阅读和修改的解决方案,而不会出现意外的令人惊讶的结果和问题。我渴望有人审查,但该项目是 NDA,因此无法分享。

以上是关于如何比较 React Hooks useEffect 上的 oldValues 和 newValues?多次重新渲染的主要内容,如果未能解决你的问题,请参考以下文章

如何比较 React Hooks useEffect 上的 oldValues 和 newValues?多次重新渲染

hooks中,useState异步问题解决方案

如何使用 React Hooks 单独切换手风琴面板?

如何使用 React Hooks 实现复杂组件的状态管理

基于 Webpack 4 和 React hooks 搭建项目

ReactReact全家桶React Hooks