Reactjs 对于调用setter函数而没有无限渲染循环的子级,如何使用带有数组状态的useState钩子?

Reactjs 对于调用setter函数而没有无限渲染循环的子级,如何使用带有数组状态的useState钩子?,reactjs,typescript,react-hooks,Reactjs,Typescript,React Hooks,我有一个父组件,它有一个处于状态的数组。它映射到数组并将项传递给子组件 import React, { useState, useEffect } from "react"; import { Channel } from "../../../constants"; import { CommandLineArguments } from "../../../main/ipcHandlers"; import { Conversion,

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

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,{useState,useffect}来自“React”;
从“../../../constants”导入{Channel}”;
从“../../main/ipchHandlers”导入{CommandLineArguments}”;
从“./Converter/Converter”导入{Conversion,Converter};
导出函数App(){
常量[commandLineArguments,setCommandLineArguments]=useState(null);
const[conversions,setConversions]=useState([]);
函数setConversion(文件路径:字符串,部分:部分){
常数更新=转换
.filter((转换)=>conversion.filepath===filepath)
.map((转换)=>({…转换,…部分}));
const rest=conversions.filter((conversion)=>conversion.filepath!==filepath);
setConversions([…rest,…updated]);
}
useEffect(函数getCommandLineArgumentsEffect(){
异步函数asyncReadSvgFile(){
const args=await window.bridgeToMainProcessApi.invoke(Channel.GetCommandLineArguments);
常量s=(args.input | |[]).map((path)=>{
返回{filepath:path};
});
设置转换;
}
asyncReadSvgFile();
}, []);
返回(
{conversions.map((c)=>(
))}
);
}
子对象调用回调来更新转换

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>
  );
}
import React,{useffect}来自“React”;
从“lodash/fp/compose”导入compose;
从“./XmlView”导入{XmlView,XmlType,ViewType};
从“../../../constants”导入{Channel,defaultProxy}”;
从“../../process”导入{prepareSvg,convertSvg}”;
从“./App/files”导入{filenameWithoutExtension,filenameFromPath};
导出类型转换={
filepath:string;
svg?:字符串;
processedSvg?:字符串;
tgml?:字符串;
};
类型道具=转换和{
代理:字符串;
setConversion(filepath:string,conversion:Partial):void;
};
导出功能转换器(道具:道具){
const{filepath,svg,processedSvg,tgml,proxy,setConversion}=props;
使用效果(
函数readSvgFileEffect(){
console.log(“read1”);
异步函数asyncReadSvgFile(){
console.log(“read2”);
const files=await window.bridgeToMainProcessApi.invoke(Channel.ReadFiles[filepath]);
const svg=文件[0]。内容;
setConversion(文件路径,{svg});
}
asyncReadSvgFile();
},
[文件路径]
);
使用效果(
函数preparesvgeeffect(){
异步函数asyncprepareSvg(){
如果(!svg){
返回;
}
const processedSvg=await prepareSvg(svg,defaultProxy?defaultProxy:proxy);
setConversion(文件路径,{processedSvg});
}
asyncprepareSvg();
},
[svg]
);
使用效果(
函数convertSvgEffect(){
异步函数asyncConvertSvg(){
如果(!processedSvg){
返回;
}
const tgml=wait convertSvg(processedSvg,compose(filenameWithoutExtension,filenameFromPath)(filepath));
setConversion(文件路径,{tgml});
}
asyncConvertSvg();
},
[processedSvg]
);
返回(
{svg&&}
{processedSvg&&}
{tgml&&}
{svg&&}
{processedSvg&&}
{tgml&&}
);
}
我不明白为什么这会导致无限渲染循环。我知道调用
setConversions
会导致父级重新渲染并将新道具传递给子级。我想这可能会导致所有的孩子都从头开始重新创作。请随时对正在发生的事情提供更好的解释


不管怎样,我的主要问题是:如何避免无限重渲染?

我试图重现错误,但未能成功。即使在异步之后对转换进行重新排序,也不会无限地重新渲染,而是将转换按随机顺序进行

我更改了一些代码以进行优化,比如不随机化转换和使转换成为纯组件,因为它将在其他转换发生更改时进行渲染,这将使它渲染的次数比数组中较大的转换次数多(可能会出现错误,但没有尝试)

这些评论就是我做出改变的地方

const later=(值)=>
新承诺((决议)=>
setTimeout(()=>解析(值),Math.random()*100)
);
//使用备忘录,这样只有当道具改变时才会重新渲染
const Converter=React.memo(功能转换器(道具){
常数{
文件路径,
setConversion,
svg,
处理SVG,
}=道具;
反作用(
函数readSvgFileEffect(){
稍后({svg:{val:'svg'}})。然后((解析)=>
setConversion(文件路径,解析)
);
},
//添加依赖项
[文件路径,设置转换]
);
反作用(
函数preparesvgeeffect(){
如果(!svg){
返回;
}
后来({
processedSvg:{val:'processed'},
})。然后((解析)=>
setConversion(文件路径,解析)
);
},
//添加依赖项
[文件路径,设置转换,svg]
);
反作用(
函数convertSvgEffect(){
如果(!processedSvg){
返回;
}
后来({
tgml:{val:'tgml'},
})。然后((解析)=>
setConversion(文件路径,解析)
);
},
//添加依赖项
[filepath、processedSvg、setConversion]
);
返回{JSON.stringify(props,null,2)};
});
函数App(){
const[conversions,setConversions]=React.useState([]);
React.useffect(函数getCommandLineArgumentsEffect(){
稍后()。然后(()=>
集合转换([
{filepath:'1'},
{filepath:'2'},
{filepath:'3'},
])
);
}, []);
//使用useCallback,使setConversion不会更改
const setConversion=React.useCallback(
函数集转换(文件路径,部分){
//传递回调