React - 使用道具时重新渲染太多

React - Too many re-renders when using props

我一直在做一个在屏幕上显示图表的项目。现在,App.js 中的 data 状态作为 prop 传递,然后导致 infinite loop error.

如果数据变量在 Graph.js 文件中并在 useState 中使用和定义,则没有问题。

我有点不确定为什么会这样,因为我对反应还比较陌生,而且 javascript。

目标是将数据添加到图表中,它会加载,然后当我更改 App.js 文件中的数据时,页面将自动再次加载新数据。

任何help/advice将不胜感激:)

App.js

function App() {
  const [data, setData] = useState(null)
  setData(
    {
    nodes:[
        {name:"Max"},
        {name:"George"},
        {name:"Jesus"},
        {name:"Ben"},
        {name:"James"},
        {name:"Sam"},
        {name:"Sassms"}
    ],

    edges:[
        {source:"Max", target:"George"},
        {source:"George", target:"Jesus"},
        {source:"Jesus", target:"Max"},
        {source:"Jesus", target:"Ben"},
        {source:"James", target:"Ben"},
        {source:"Sam", target:"Sassms"},
        {source:"Sam", target:"Ben"}
    ]
    })
    console.log(data)

  return (
    <Graph colour="Grey" data={data} setData={setData}/>
  );
}

Graph.js

function Graph(props) {
    const svgRef = useRef(null);
    useEffect(
        () => {
            let svg = d3.select(svgRef.current)
            if(svgRef.current) {// Check that svg element has been rendered
                let width = svg.attr("width")
                let height = svg.attr("height")

                d3.selectAll("svg > *").remove();

                let edge = svg
                    .append("g")
                    .selectAll("line")
                    .data(props.data.edges)
                    .enter()
                    .append("line")
                    .attr("stroke-width", function(d) {
                        return 3;
                    })
                    .style("stroke", "black")

                let node = svg
                    .append("g")
                    .selectAll("circle")
                    .data(props.data.nodes)
                    .enter()
                    .append("circle")
                    .attr("r", 7)
                    .attr("fill", function(d) {
                        return "grey";
                    })
                    .attr("stroke", "black")
            }

    }, [props.data])

    
    return (
        <svg ref={svgRef} id="svgID" width="1000" height="900"></svg>
    )
  }

这是因为您一次又一次地更新每个渲染器的状态。如果您只是简单地在函数内部调用 setData(),它会调用此 setter 调用重新渲染并触发 React 再次调用您的 App 函数。

如果你想有一个默认状态,你可以像下面那样做。如果你想更新状态,应该通过其他动作来完成,比如点击一个按钮,获取一些数据等。

查看下面的演示,我在其中添加了一个按钮,用于向 nodes 数组添加新名称:

function App() {
  const [data, setData] = React.useState({
    nodes: [
      { name: 'Max' },
      { name: 'George' },
      { name: 'Jesus' },
      { name: 'Ben' },
      { name: 'James' },
      { name: 'Sam' },
      { name: 'Sassms' },
    ],
    edges: [
      { source: 'Max', target: 'George' },
      { source: 'George', target: 'Jesus' },
      { source: 'Jesus', target: 'Max' },
      { source: 'Jesus', target: 'Ben' },
      { source: 'James', target: 'Ben' },
      { source: 'Sam', target: 'Sassms' },
      { source: 'Sam', target: 'Ben' },
    ],
  });

  return <Graph colour="Grey" data={data} setData={setData} />;
}

function Graph(props) {
  function addRandomName() {
    props.setData({
      ...props.data,
      nodes: [
        ...props.data.nodes,
        { name: Math.random().toString(36).slice(2) },
      ],
    });
  }

  return (
    <React.Fragment>
      <button onClick={addRandomName}>Add new name</button>
      <h2>Names</h2>
      <p> {props.data.nodes.map((d) => d.name).join(', ')}
      </p>
      <h2>State</h2>
      <pre>{JSON.stringify(props.data, null, 2)}</pre>
    </React.Fragment>
  );
}

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
<div id="root"></div>
<script src="https://unpkg.com/react/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.production.min.js"></script>