React Nivo CirclePacking点击页面气泡异常重排问题求助
问题描述
- 使用React Nivo的
CirclePacking组件展示三级数据层级:Year(depth 0)> Province(depth 1)> City(depth 2) - 图表渲染正常,但点击页面任意位置(图表内外)时气泡都会发生重排
- 移除Year层级,改为二级层级(Province(depth 0)> City(depth 1))后,问题消失
问题根源
问题出在labelsFilter回调函数中:
- 该函数内部调用了
setZoomedNode和setNodeZero修改组件状态 labelsFilter会在图表每次渲染(包括页面点击触发的组件更新)时执行,状态更新会触发组件重渲染,进而导致气泡重排- 三级层级下状态更新的触发更频繁,而二级层级时逻辑触发条件不满足,因此问题消失
解决方案
- 将初始自动设置
zoomedNode的逻辑从labelsFilter移到useEffect中,避免在过滤函数中修改状态 - 让
labelsFilter仅负责标签显示逻辑判断,不执行任何状态更新操作 - 可选:使用
React.memo包裹组件或优化状态依赖,减少不必要的重渲染
修改后的组件代码示例
const BubbleChart = (props) => { const clickTimer = useRef(); const [nivoObject, setNivoObject] = useState({}); const [bigData, setBigData] = useState({}); const [hierarchyCopy, setHierarchyCopy] = useState(); const [selectedSchoolId, setSelectedSchooId] = useState(); const [zoomedNode, setZoomedNode] = useState(); const [nodeZero, setNodeZero] = useState(); const [selectedNode, setSelectedNode] = useState(); const [selectedNodeArPath, setSelectedNodeArPath] = useState(); const [finalDepth, setFinalDepth] = useState(2); // 初始化设置默认zoomedNode useEffect(() => { if (nivoObject.children && !zoomedNode) { const rootNode = { ...nivoObject, depth: 0 }; setNodeZero(rootNode); setZoomedNode(rootNode); } }, [nivoObject, zoomedNode]); return ( <ResponsiveCirclePacking data={nivoObject} id="id" value="nodeSize" margin={{ top: 20, right: 20, bottom: 20, left: 20 }} padding={13} zoomedId={zoomedNode?.id} onClick={(node, event) => { setSelectedNode(node); clearTimeout(clickTimer.current); if (event.detail === 1) { clickTimer.current = setTimeout(() => { setZoomedNode(zoomedNode?.id === node.id ? null : node); }, 300); } }} enableLabels={true} label={(dat) => `${bigData[dat.data.source][dat.data.objId]["title"]}`} labelComponent={(labelProps) => { return ( <HighChartCustomeLabel {...labelProps} zoomedNode={zoomedNode} submitHandler={nodeClickHandler} finalDepth={finalDepth} /> ); }} labelsFilter={(label) => { // 仅做过滤判断,不修改状态 if (zoomedNode?.depth === finalDepth) { return label.node.depth === zoomedNode?.depth; } else { return label.node.depth === zoomedNode?.depth + 1; } }} labelsSkipRadius={10} labelTextColor="#fff" tooltip={CustomTooltip} borderWidth={0} colorBy="depth" colors={["#4880FF33", "#00A353cc", "#52477199", "#493F6499"]} motionConfig="slow" /> ); };
原始数据结构
{"id": "DATA0","source": "DATA","objId": "DATA0","children": [{"id": "AY2122","source": "ACADEMIC_YEAR","objId": "AY2122","children": [{"id": "AY2122-PROVINCE1","source": "PROVINCE","objId": "PROVINCE1","children": [{"id": "AY2122-PROVINCE1-CITY34","source": "CITY","objId": "CITY34","buildings": [],"nodeSize": 10},{"id": "AY2122-PROVINCE1-CITY35","source": "CITY","objId": "CITY35","buildings": [{"source": "BUILDING","objId": "19"},{"source": "BUILDING","objId": "48"}],"nodeSize": 30}]},{"id": "AY2122-PROVINCE2","source": "PROVINCE","objId": "PROVINCE2","children": [{"id": "AY2122-PROVINCE2-CITY4","source": "CITY","objId": "CITY4","buildings": [],"nodeSize": 10},{"id": "AY2122-PROVINCE2-CITY18","source": "CITY","objId": "CITY18","buildings": [{"source": "BUILDING","objId": "8"}],"nodeSize": 20}]},{"id": "AY2122-PROVINCE3","source": "PROVINCE","objId": "PROVINCE3","children": [{"id": "AY2122-PROVINCE3-CITY33","source": "CITY","objId": "CITY33","buildings": [{"source": "BUILDING","objId": "77"},{"source": "BUILDING","objId": "108"},{"source": "BUILDING","objId": "189"}],"nodeSize": 40}]}]},{"id": "AY2223","source": "ACADEMIC_YEAR","objId": "AY2223","children": [{"id": "AY2223-PROVINCE1","source": "PROVINCE","objId": "PROVINCE1","children": [{"id": "AY2223-PROVINCE1-CITY29","source": "CITY","objId": "CITY29","buildings": [{"source": "BUILDING","objId": "29"},{"source": "BUILDING","objId": "46"}],"nodeSize": 30},{"id": "AY2223-PROVINCE1-CITY34","source": "CITY","objId": "CITY34","buildings": [],"nodeSize": 10}]},{"id": "AY2223-PROVINCE2","source": "PROVINCE","objId": "PROVINCE2","children": [{"id": "AY2223-PROVINCE2-CITY4","source": "CITY","objId": "CITY4","buildings": [],"nodeSize": 10},{"id": "AY2223-PROVINCE2-CITY20","source": "CITY","objId": "CITY20","buildings": [{"source": "BUILDING","objId": "198"}],"nodeSize": 20}]},{"id": "AY2223-PROVINCE3","source": "PROVINCE","objId": "PROVINCE3","children": [{"id": "AY2223-PROVINCE3-CITY33","source": "CITY","objId": "CITY33","buildings": [{"source": "BUILDING","objId": "77"},{"source": "BUILDING","objId": "108"}],"nodeSize": 30}]}]}]}
原始组件代码
const BubbleChart =(props)=>{ const clickTimer = useRef(); const [nivoObject, setNivoObject] = useState({}); const [bigData, setBigData] = useState({}); const [hierarchyCopy, setHierarchyCopy] = useState(); const [selectedSchoolId, setSelectedSchooId] = useState(); const [zoomedNode, setZoomedNode] = useState(); const [nodeZero, setNodeZero] = useState(); const [selectedNode, setSelectedNode] = useState(); const [selectedNodeArPath, setSelectedNodeArPath] = useState(); const [finalDepth, setFinalDepth] = useState(2); return ( <ResponsiveCirclePacking data={nivoObject} id="id" value="nodeSize" margin={{ top: 20, right: 20, bottom: 20, left: 20 }} padding={13} zoomedId={zoomedNode?.id} onClick={(node, event) => { setSelectedNode(node); clearTimeout(clickTimer.current); if (event.detail === 1) { // On single click clickTimer.current = setTimeout(() => { setZoomedNode(zoomedNode?.id === node.id ? null : node); }, 300); } }} enableLabels={true} label={(dat) => true && `${bigData[dat.data.source][dat.data.objId]["title"]}`} labelComponent={(labelProps) => { return ( <HighChartCustomeLabel {...labelProps} zoomedNode={zoomedNode} submitHandler={nodeClickHandler} finalDepth={finalDepth} /> ); }} labelsFilter={(label) => { if (label.node.depth === 0 && !zoomedNode) { setNodeZero(label.node); setZoomedNode(label.node); } if (zoomedNode?.depth === finalDepth) { return (label.node.depth === zoomedNode?.depth); } else { return (label.node.depth === zoomedNode?.depth + 1); } }} labelsSkipRadius={10} labelTextColor="#fff" tooltip={CustomTooltip} borderWidth={0} colorBy="depth" colors={["#4880FF33", "#00A353cc", "#52477199", "#493F6499"]} motionConfig="slow" /> ); };
内容的提问来源于stack exchange,提问作者Abdallahsh96
相关产品推荐
相关产品推荐

