首页 > 解决方案 > 带有数组道具的自定义钩子正在无限循环

问题描述

我知道这里有很多类似的问题,但我无法找到让我意识到我在这里做错了什么的答案。

我制作了一个名为的自定义钩子,如果当前状态有百万作为乘数useAmounts,它可以将文本5%转换为0.05500转换为。500,000,000我需要这个用于各种组件中的多个输入。

我还制作了另一个名为的钩子useMerged,它基本上有一个输入数据数组,例如"5%", "200 million"加上一些额外的数据。然后该钩子应将解析后的数字合并到所需的对象中。

最后,我想要一个对象列表,我可以在其中检索值和函数以更新列表中的值。但我得到了无限循环。

我试图做一个最小的例子,但是对于我正在寻找的模式来说,很难让它完全最小化。任何帮助深表感谢!我的猜测是有一个更好的模式来实现我在这里尝试的。

import * as React from "react";

interface Amount {
  multiplier: number;
  displayText: string;
  value: number;
  updateAmount: (text: string) => void;
}

function useAmounts(initialValues: string[]): Amount[] {
  const [amounts, setAmounts] = React.useState<
    {
      multiplier: number;
      displayText: string;
      value: number;
    }[]
  >([]);

  React.useEffect(() => {
    console.log(
      `useAmounts: Setting initial amounts of length ${initialValues.length}`
    );
    setAmounts(
      initialValues.map(text => {
        const matches = text.match(/\d+/g);
        const multiplier = text.includes("%") ? 0.01 : 1;
        const displayText = matches ? matches[0] : "";
        return {
          multiplier,
          displayText,
          value: parseFloat(displayText) * multiplier
        };
      })
    );
  }, [initialValues]);

  const updateAmount = (index: number) => (text: string) => {
    const matches = text.match(/\d+/g);
    setAmounts(prevState => {
      const newState = [...prevState];
      newState[index].displayText = matches ? matches[0] : "";
      return newState;
    });
  };

  return amounts.map((a, index) => {
    return {
      multiplier: a.multiplier,
      displayText: a.displayText,
      value: a.value,
      updateAmount: updateAmount(index)
    };
  });
}

interface State {
  text: string;
  additionalValue: string;
}

interface MergedState {
  amount: {
    multiplier: number;
    displayText: string;
    value: number;
    updateAmount: (text: string) => void;
  };
  additionalValue: string;
}

function useMerged(states: State[]) {
  const [initialText, setInitialText] = React.useState<string[]>([]);
  const amounts = useAmounts(initialText);
  const [mergedState, setMergedState] = React.useState<MergedState[]>([]);

  React.useEffect(() => {
    console.log(
      `useMerged: setting initial input for useAmounts in useMerged of length ${
        states.length
      }`
    );
    setInitialText(states.map(v => v.text));
  }, [states]);

  React.useEffect(() => {
    if (amounts.length === states.length)
      setMergedState(
        states.map((s, index) => {
          return {
            additionalValue: s.additionalValue,
            amount: amounts[index]
          };
        })
      );
  }, [amounts.length, states.length]);

  return mergedState;
}

const delayedInitialState: State[] = [
  { text: "100 million", additionalValue: "A" },
  { text: "25%", additionalValue: "B" }
];

export default function App() {
  const [state, setState] = React.useState<State[]>([]);
  const mergedStates = useMerged(state);

  React.useEffect(() => {
    console.log("App: setting initial state on app level");
    setState(delayedInitialState);
  }, []);

  return (
    <div className="App">
      <ul>
        {mergedStates.map((a, index) => (
          <li key={index}>
            <input
              value={a.amount.displayText}
              onChange={event => a.amount.updateAmount(event.target.value)}
            />
            {a.amount.multiplier}
          </li>
        ))}
      </ul>
    </div>
  );
}

编辑

我也对这里的生命周期感到困惑。控制台输出:

  1. useAmounts:设置长度为 0 的初始数量
  2. useMerged: 为 useMerged 中长度为 0 的 useAmounts 设置初始输入
  3. 应用程序:在应用程序级别设置初始状态
  4. useAmounts:设置长度为 0 的初始数量
  5. useMerged: 为 useMerged 中长度为 2 的 useAmounts 设置初始输入
  6. useAmounts:设置长度为 2 的初始数量

为什么之前useAmounts的渲染改变了初始输入?App useMerged

标签: reactjstypescript

解决方案


在你的useMerged函数中,amounts每次你做的时候都会有不同的参考const amounts = useAmounts(states.map(v => v.text));。这意味着当您签入useEffect以查看initialValues您的函数是否已更改时useAmounts,它将始终将新引用视为更改。

解决此问题的一种方法-您可以改为useAmounts(states)将代码更新为 aState[]而不是 a string[],而不是调用map它吗?


推荐阅读