React与D3的useEffect中刷选交互触发无限循环的问题排查
修复React+D3刷选交互的无限循环问题
问题根源
你的代码里useEffect把pos放进了依赖数组,每次调用setPos更新pos,都会触发useEffect重新执行。而每次执行useEffect又会重新创建brush实例并绑定事件,刷选时再次触发setPos,形成无限循环。
修复步骤
- 调整
useEffect的依赖数组:把依赖改成空数组[],让useEffect只在组件挂载时执行一次。初始化坐标轴、创建brush这些操作不需要重复执行。 - 缓存D3比例尺:用
useMemo缓存x比例尺,避免每次组件渲染都重新创建实例,提升性能。 - 避免不必要的状态更新:在brush事件里,先对比新的
index和当前pos是否一致,一致就不调用setPos,减少无用重渲染。
修改后的代码
import { useState, useRef, useEffect, useMemo } from 'react'; import { select, brushX, brushSelection, scaleLinear, axisBottom } from 'd3'; const Timeline = () => { const svgRef = useRef(null!); const brushRef = useRef(null!); // 用useMemo缓存比例尺,避免重复创建 const x = useMemo(() => scaleLinear().domain([0, 1000]).range([10, 810]), []); // 初始化pos默认值,避免渲染时出现undefined const [pos, setPos] = useState([0, 100]); useEffect(() => { const svg = select(svgRef.current); // 初始化坐标轴 svg.select('.x-axis') .attr('transform', `translate(0,${110})`) .call(axisBottom(x)); const brush = brushX() .extent([ [10, 10], [810, 110], ]) .on('start brush end', function () { const nodeSelection = brushSelection(select(brushRef.current).node()); if (!nodeSelection) return; // 处理刷选取消的边界情况 const index = nodeSelection.map(x.invert); // 对比当前状态,避免无意义更新 if (index[0] !== pos[0] || index[1] !== pos[1]) { setPos(index); } }); // 初始化brush的初始位置 select(brushRef.current).call(brush).call(brush.move, [0, 100].map(x)); // 清理函数:组件卸载时移除事件监听,防止内存泄漏 return () => { select(brushRef.current).call(brush.on('start brush end', null)); }; }, []); // 依赖数组设为空,仅组件挂载时执行一次 return ( <svg ref={svgRef} width="1200" height={800}> <rect x={x(200)} y={10} width={400} height={100} fill={'blue'} /> <g className="x-axis" /> <g ref={brushRef} /> {/* 给文本添加位置和数值格式化,避免重叠和显示异常 */} <text x={10} y={150}>{pos[0].toFixed(2)}-{pos[1].toFixed(2)}</text> </svg> ); }; export default Timeline;
额外说明
- 新增清理函数,组件卸载时移除brush的事件监听,避免内存泄漏。
- 给
pos设置初始值,解决渲染阶段文本显示undefined的问题。 - 调整文本元素的位置并格式化数值,提升显示效果和可读性。
内容的提问来源于stack exchange,提问作者user990463
相关产品推荐
相关产品推荐

