如何在没有无限渲染循环的情况下将useState挂钩与数组状态一起用于调用setter函数的子级?

时间:2020-06-29 20:26:51

标签: reactjs typescript react-hooks

我有一个状态为数组的父组件。它映射到数组,并将项目传递给子组件。

import React, { useState, useEffect } from "react";
import { Channel } from "../../../constants";
import { CommandLineArguments } from "../../../main/ipcHandlers";
import { Conversion, Converter } from "../Converter/Converter";

export function App() {
  const [commandLineArguments, setCommandLineArguments] = useState<null | CommandLineArguments>(null);
  const [conversions, setConversions] = useState<Conversion[]>([]);

  function setConversion(filepath: string, partial: Partial<Conversion>) {
    const updated = conversions
      .filter((conversion) => conversion.filepath === filepath)
      .map((conversion) => ({ ...conversion, ...partial }));
    const rest = conversions.filter((conversion) => conversion.filepath !== filepath);
    setConversions([...rest, ...updated]);
  }

  useEffect(function getCommandLineArgumentsEffect() {
    async function asyncReadSvgFile() {
      const args = await window.bridgeToMainProcessApi.invoke(Channel.GetCommandLineArguments);
      const s = (args.input || []).map((path) => {
        return { filepath: path };
      });
      setConversions(s);
    }
    asyncReadSvgFile();
  }, []);

  return (
    <div>
      {conversions.map((c) => (
        <Converter
          proxy=""
          setConversion={setConversion}
          key={c.filepath}
          filepath={c.filepath}
          svg={c.svg}
          processedSvg={c.processedSvg}
          tgml={c.tgml}
        />
      ))}
    </div>
  );
}

子级调用回调以更新转换。

import React, { useEffect } from "react";
import compose from "lodash/fp/compose";
import { XmlView, XmlType, ViewType } from "../XmlView";
import { Channel, defaultProxy } from "../../../constants";
import { prepareSvg, convertSvg } from "../../../process";
import { filenameWithoutExtension, filenameFromPath } from "../App/files";

export type Conversion = {
  filepath: string;
  svg?: string;
  processedSvg?: string;
  tgml?: string;
};

type Props = Conversion & {
  proxy: string;
  setConversion(filepath: string, conversion: Partial<Conversion>): void;
};

export function Converter(props: Props) {
  const { filepath, svg, processedSvg, tgml, proxy, setConversion } = props;
  useEffect(
    function readSvgFileEffect() {
      console.log("read1");
      async function asyncReadSvgFile() {
        console.log("read2");
        const files = await window.bridgeToMainProcessApi.invoke(Channel.ReadFiles, [filepath]);
        const svg = files[0].content;
        setConversion(filepath, { svg });
      }
      asyncReadSvgFile();
    },
    [filepath]
  );

  useEffect(
    function prepareSvgEffect() {
      async function asyncprepareSvg() {
        if (!svg) {
          return;
        }
        const processedSvg = await prepareSvg(svg, defaultProxy ? defaultProxy : proxy);
        setConversion(filepath, { processedSvg });
      }
      asyncprepareSvg();
    },
    [svg]
  );

  useEffect(
    function convertSvgEffect() {
      async function asyncConvertSvg() {
        if (!processedSvg) {
          return;
        }
        const tgml = await convertSvg(processedSvg, compose(filenameWithoutExtension, filenameFromPath)(filepath));
        setConversion(filepath, { tgml });
      }
      asyncConvertSvg();
    },
    [processedSvg]
  );

  return (
    <div>
      {svg && <XmlView serialized={svg} xmlType={XmlType.Svg} viewType={ViewType.Image} />}
      {processedSvg && <XmlView serialized={processedSvg} xmlType={XmlType.ProcessedSvg} viewType={ViewType.Image} />}
      {tgml && <XmlView serialized={tgml} xmlType={XmlType.Tgml} viewType={ViewType.Image} />}
      {svg && <XmlView serialized={svg} xmlType={XmlType.Svg} viewType={ViewType.Code} />}
      {processedSvg && <XmlView serialized={processedSvg} xmlType={XmlType.ProcessedSvg} viewType={ViewType.Code} />}
      {tgml && <XmlView serialized={tgml} xmlType={XmlType.Tgml} viewType={ViewType.Code} />}
    </div>
  );
}

我不明白为什么这会导致无限的渲染循环。我了解致电setConversions会使父母重新渲染并将新的道具传递给孩子。我想这可能会导致所有孩子重新创建。随时提供更好的解释。

无论如何,我的主要问题是:如何解决无限重新渲染的问题?

1 个答案:

答案 0 :(得分:1)

我试图重现该错误,但未能成功。甚至在异步之后对转换进行重新排序也不会无限地重新呈现,但是确实将转换置于随机顺序。

我更改了一些代码以进行优化,例如不对转化进行随机化,并使Conversion成为纯组件,因为只要其他转化发生变化,它就会呈现,这会使它呈现的次数比较大的conversions数组得到的次数还要多(可能直到错误发生的地步)但没有尝试。)

评论是我进行更改的地方。

const later = (value) =>
  new Promise((resolve) =>
    setTimeout(() => resolve(value), Math.random() * 100)
  );
//using memo so it will only re render if props change
const Converter = React.memo(function Converter(props) {
  const {
    filepath,
    setConversion,
    svg,
    processedSvg,
  } = props;
  React.useEffect(
    function readSvgFileEffect() {
      later({ svg: { val: 'svg' } }).then((resolve) =>
        setConversion(filepath, resolve)
      );
    },
    //added dependencies
    [filepath, setConversion]
  );
  React.useEffect(
    function prepareSvgEffect() {
      if (!svg) {
        return;
      }
      later({
        processedSvg: { val: 'processed' },
      }).then((resolve) =>
        setConversion(filepath, resolve)
      );
    },
    //added dependencies
    [filepath, setConversion, svg]
  );
  React.useEffect(
    function convertSvgEffect() {
      if (!processedSvg) {
        return;
      }
      later({
        tgml: { val: 'tgml' },
      }).then((resolve) =>
        setConversion(filepath, resolve)
      );
    },
    //added dependencies
    [filepath, processedSvg, setConversion]
  );
  return <pre>{JSON.stringify(props, null, 2)}</pre>;
});

function App() {
  const [conversions, setConversions] = React.useState([]);

  React.useEffect(function getCommandLineArgumentsEffect() {
    later().then(() =>
      setConversions([
        { filepath: '1' },
        { filepath: '2' },
        { filepath: '3' },
      ])
    );
  }, []);
  //use useCallback so setConversion doesn't change
  const setConversion = React.useCallback(
    function setConversion(filepath, partial) {
      //pass callback to set state function so conversions
      //  is not a dependency of useCallback
      setConversions((conversions) =>
        //do not re order conversions
        conversions.map((conversion) =>
          conversion.filepath === filepath
            ? {
                ...conversion,
                ...partial,
              }
            : conversion
        )
      );
    },
    []
  );
  return (
    <div>
      {conversions.map((c) => (
        <Converter
          setConversion={setConversion}
          key={c.filepath}
          filepath={c.filepath}
          svg={c.svg}
          processedSvg={c.processedSvg}
          tgml={c.tgml}
        />
      ))}
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>


<div id="root"></div>