具有数组道具的自定义钩子使无限循环

时间:2020-02-06 16:03:52

标签: reactjs typescript

我知道这里有很多类似的问题,但是我找不到能够使我意识到我在做什么错的答案。

我制作了一个名为useAmounts的自定义钩子,如果当前状态为百万,则可以将类似5%的文本转换为0.05或将500转换为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:设置useAerges中长度为0的useAmounts的初始输入
  3. 应用程序:在应用程序级别设置初始状态
  4. useAmounts:设置长度为0的初始量
  5. useMerged:设置长度为2的useMerged中useAmounts的初始输入
  6. useAmounts:设置长度2的初始量

为什么useAmountsApp之后之前 useMerged进行了渲染?

1 个答案:

答案 0 :(得分:1)

在您的useMerged函数中,每次执行amounts时,const amounts = useAmounts(states.map(v => v.text));将是一个不同的引用。这就是说,当您签入useEffect来查看initialValues函数中的useAmounts是否已更改时,它将始终将新引用视为更改。

解决此问题的一种方法-您是否可以只useAmounts(states)并将代码更新为State[]而不是string[],而不是调用map