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(
函数集转换(文件路径,部分){
//传递回调