簡體   English   中英

子組件問題的多次重新渲染

[英]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>
    );
  }
}

filesplots是:

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 認為事情已經更新,但流程非常循環。

讀筆會更好地解釋。 https://codepen.io/windowsill/pen/BaJzRmY

當您將 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,它只會使您的流程復雜化。

簡述修改步驟:

  1. 您從Parent傳遞plot道具作為Plot組件的坐標數據。
  2. 通過onEditGate回調function,讓Parent組件獲取到坐標移動后的信息,進而修改坐標值。
  • 結果:每個Plot組件更新一次,代碼變得更干凈。
    在此處輸入圖像描述 (您可以查看底部的完整顯示(CodeSandbox)。)

代碼片段(根據您的代碼略作修改)

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;

完整代碼示例(根據您的代碼略作修改)

編輯 TextField selectionStart

希望能幫到你:)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM