[英]Multiple re-rendering of child component problem
我有一個名為 Plot.js 的子組件。 這需要一個道具plot
,它是一個 object 屬性points
,它包含一個 x,y 點數組。 我還有一組files
。 循環遍歷files
,並根據 plot.gate.points 中的點在plot.gate.points
上繪制每個文件的多邊形:
父組件如下所示:
class Parent extends React.Component {
constructor(props) {
super(props)
this.state = {
plots: plots.plotList,
files: files.fileList
}
this.onEditGate = this.onEditGate.bind(this);
}
onEditGate = (change) => {
this.state.plots[change.plotIndex] = change.plot;
console.log("setting the state");
this.setState({
files: files,
plots: this.state.plots
});
};
render() {
return (
<table className="workspace">
<tbody>
{files.fileList.map((file, fileIndex) => {
return (
<tr key={`tr-${fileIndex}`}>
{plots.plotList.map((plot, plotIindex) => {
return (
<td key={`td-${plotIindex}`}>
<Plot
plotIndex={`${fileIndex}-${plotIindex}`}
plot={plot}
file={file}
onEditGate={this.onEditGate}
></Plot>
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
);
}
}
files
和plots
是:
let plots = JSON.parse('{"plotList":[{"population":"All","gate":{"points":[[10,10],[70,10],[70,70],[10,70]]}}]}');
let files = JSON.parse('{"fileList":[{"file":"a"},{"file":"b"}]}');
而 Plot 是:
function Plot(props) {
console.log('in function Plot, plotIndex is ' + props.plotIndex + ', props.plot points are ', props.plot.gate.points);
const [localPlot, setLocalPlot] = React.useState(props.plot);
React.useEffect(() => {
setLocalPlot(props.plot);
console.log('in useEffect, plotIndex is ' + props.plotIndex + ', localPlot points are ', localPlot.gate.points);
const context = getContext(props.plotIndex);
context.clearRect(0, 0, 200, 200);
context.fillStyle = "white";
drawPolygonLine(context, localPlot);
}, [localPlot, props.plot, props.file]);
const drawPolygonLine = (context, plot) => {
context.strokeStyle = "red";
context.lineWidth = 1;
context.beginPath();
// draw the first point of the gate
context.moveTo(plot.gate.points[0][0], plot.gate.points[0][1]);
plot.gate.points.forEach((pointOnCanvas) => {
context.lineTo(pointOnCanvas[0], pointOnCanvas[1]);
});
context.closePath();
context.stroke();
};
const movePolygon = (plotIndex) => {
localPlot.gate.points = props.plot.gate.points.map((point) => {
return [point[0] + 40, point[1] + 40];
});
setLocalPlot(JSON.parse(JSON.stringify(localPlot)));
let change = {
type: "EditPolygon",
plot: localPlot,
plotIndex: props.plotIndex.split("-")[1],
points: JSON.parse(JSON.stringify(localPlot.gate.points)),
};
props.onEditPolygon(change);
}
return (
<div>
<canvas
style={{ border: "thick solid #32a1ce" }}
className="canvas"
id={`canvas-${props.plotIndex}`}
width={200}
height={200}
/>
<button
onClick={() => movePolygon(props.plotIndex)}
>
Move plot {props.plotIndex}
</button>
</div>
);
}
我需要復制plot
(稱為localPlot
)。 這是因為用戶可以在 plot 之一上操作多邊形。當鼠標移動時,我得到新的點,然后執行setLocalPlot(JSON.parse(JSON.stringify(localPlot)));
. 這僅重新呈現我正在操作的 plot 的實例。 在鼠標向上時,我將最終的多邊形點傳播回 State,並重新渲染所有內容,遍歷文件並使用新的多邊形點創建繪圖。 所以這些圖看起來都一樣。
這一切都很好。 我創建了一個簡化的 CodePen。 省略了在鼠標移動時操作的僅 PLOT 實例的重新渲染(因為這工作正常)。 相反,我添加了一個“移動”按鈕來顯示當我傳播回 State 時會發生什么。代碼筆在這里: https://codepen.io/mark-kelly-the-looper/pen/WNdrRBj?editors =0011
但是,當我查看 console.log 輸出時,我看到在更新 State 之后,每個 Plot 組件實例重新呈現 3 次,而不是我預期的一次,請參閱代碼筆日志:
問題是我的應用程序在useEffect()
中做了大量工作,計算了數千個 x、y 點的 position,以及多邊形的 position。 因此,性能非常重要。 為什么每個實例重新渲染 3 次? 有更好的方法嗎?
編輯:我有localPlot
的原因是用戶拖動,我需要重新渲染 Plot 的實例,我需要useEffect()
來觸發,因為useEffect()
是 canvas 上所有繪圖發生的地方. 如果有另一種方式來顯示被拖動的多邊形,那么我洗耳恭聽......
編輯我在這里添加了一個更全面的代碼筆,您可以在其中看到在拖動多邊形時重新渲染 Plot 實例。 拖動多邊形並在鼠標上移后,您可以看到 Plot 組件的每個實例重新加載 3 次而不是一次:
如上所述,這對我的應用程序來說是不可接受的,因為在每個Plot
實例的useEffect()
中我進行了數千次計算(此處未包括在內)。 我開始認為,從根本上說,這是 React 本身的局限性? 代碼筆在這里: https://codepen.io/mark-kelly-the-looper/pen/bGYzZQR?editors=0011 。
您在依賴於 localPlot 的效果中設置 localPlot,因此它總是至少要渲染兩次。 通常這是一個無限循環。
您還具有未使用的此效果的其他依賴項。 刪除它們會刪除不必要的渲染。
React.useEffect(() => {
console.log('in useEffect', Date.now());
const context = getContext(props.plotIndex);
context.clearRect(0, 0, 200, 200);
context.fillStyle = "white";
drawGateLine(context, localPlot);
}, [localPlot]);
好吧,我瀏覽了渲染的蜘蛛網,並讓繪圖效果在傳播發生時為每個實例只運行一次。
我也必須更新 setStates 以刪除所有突變。
上面的每個評論仍然是正確的; 永遠不需要更新效果對該效果的依賴性。 這總是一個錯誤。
由於偏移量 state 在模塊上的方式(因此 React 不知道它),你不得不欺騙 React 認為事情已經更新,但流程非常循環。
當您將 localPlot 和 props.plot 放入 useEffect 的依賴列表中時,React 將在每次渲染后將這些變量與它們之前的值進行淺層比較,以決定是否運行鈎子內的代碼。 您應該比較這些對象的內容,而不是它們的 object 引用。
此外,useEffect 鈎子在每次渲染之后運行,而不是在渲染之前運行,因此在 useEffect 鈎子中使用 setState 設置 state 總是會導致額外的渲染,所以最好不要把它放在那里。
解決方案是在 useEffect 掛鈎之外的每個渲染器上對 localPlot 和 props.plot 進行深入比較,並且僅在它們不同時更新 localPlot:
const plotsAreSame = (plot1, plot2) => {
if (plot1.population != plot2.population) return false;
return (plot1.gate.points.length===plot2.gate.points.length && plot1.gate.points.every(
(p,i)=>(p[0]===plot2.gate.points[i][0] && p[1]===plot2.gate.points[i][1])
))
}
function Plot(props) {
console.log('in function Plot, plotIndex is ' + props.plotIndex + ', props.plot points are ', props.plot.gate.points);
const [localPlot, setLocalPlot] = React.useState(props.plot);
if (!plotsAreSame(localPlot, props.plot)) {setLocalPlot(props.plot)}
React.useEffect(() => {
console.log('in useEffect, plotIndex is ' + props.plotIndex + ', localPlot points are ', localPlot.gate.points);
const context = getContext(props.plotIndex);
context.clearRect(0, 0, 200, 200);
context.fillStyle = "white";
drawGateLine(context, localPlot);
}, [localPlot, props.plotIndex]);
這樣兩個組件只會被渲染一次。
您實際上並不需要localState
state,它只會使您的流程復雜化。
簡述修改步驟:
Parent
傳遞plot
道具作為Plot
組件的坐標數據。onEditGate
回調function,讓Parent
組件獲取到坐標移動后的信息,進而修改坐標值。function Plot({ plotIndex, plot, file, onEditGate = () => {} }) {
console.log(
"in function Plot, plotIndex is " + plotIndex + ", props.plot points are ",
plot.gate.points
);
useEffect(() => {
console.log(
"in useEffect, plotIndex is " + plotIndex + ", localPlot points are ",
plot.gate.points
);
const context = getContext(plotIndex);
context.clearRect(0, 0, 200, 200);
context.fillStyle = "white";
drawGateLine(context, plot);
}, [plot, plotIndex]);
const drawGateLine = (context, plot) => {
context.strokeStyle = "red";
context.lineWidth = 1;
context.beginPath();
// draw the first point of the gate
context.moveTo(plot.gate.points[0][0], plot.gate.points[0][1]);
plot.gate.points.forEach((pointOnCanvas) => {
context.lineTo(pointOnCanvas[0], pointOnCanvas[1]);
});
context.closePath();
context.stroke();
};
const movePolygon = (plotIndex) => {
const saveCopyLocalPlot = JSON.parse(JSON.stringify(plot));
const newLocal = saveCopyLocalPlot.gate.points.map((point) => {
return [point[0] + 40, point[1] + 40];
});
saveCopyLocalPlot.gate.points = newLocal;
let change = {
type: "EditGate",
plot: saveCopyLocalPlot,
plotIndex: plotIndex.split("-")[1],
points: JSON.parse(JSON.stringify(saveCopyLocalPlot.gate.points))
};
onEditGate(change);
};
return (
<div>
<canvas
style={{ border: "thick solid #32a1ce" }}
className="canvas"
id={`canvas-${plotIndex}`}
width={200}
height={200}
/>
<button onClick={() => movePolygon(plotIndex)}>
Move plot {plotIndex}
</button>
</div>
);
}
export default Plot;
希望能幫到你:)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.