我正在使用React在D3中创建一个图表。在我的图表中,我有一个操纵React Hooks的画笔组件。我有以下代码:-
App.js
import React, {useEffect, useState} from 'react';
import ChartWrapper from './ChartWrapper';
//import ;
function App() {
let data = [{value: 5, time: 1590034669935}, {value: 4, time: 1590034670435}, {value: 7, time: 1590034670935}, {value: 10, time: 1590034671436}, {value: 4, time: 1590034671936}, {value: 6, time: 1590034672437}, {value: 7, time: 1590034672937}, {value: 4, time: 1590034673437}, {value: 7, time: 1590034673937}, {value: 5, time: 1590034674437}];
// let [data, setData] = useState([]);
const style = {
textAlign: 'center',
};
return (
<div className="App" style={style}>
<ChartWrapper data={data} xAxisText = 'Time' yAxisText = 'Values' />
</div>
);
}
export default App;
ChartWrapper.js
import React, {useRef, useEffect, useState, useImperativeHandle} from 'react';
import {select, selectAll, extent, axisBottom, axisLeft, scaleLinear, scaleTime, curve, line, curveCardinal} from 'd3';
import {IsFirstRender} from './IsFirstRender';
import Chart from './Chart';
import NavChart from './NavChart';
import UsePrevious from './UsePrevious';
const MARGIN = {TOP: 50, BOTTOM: 50, LEFT: 50, RIGHT: 50};
const maxData = 10;
export default function ChartWrapper(props) {
let [selection, setSelection] = useState([props.data[0].time, props.data[1].time]);
const previousSelection = UsePrevious(selection);
// let [isDefaultSelection, setDefaultSelection] = useState(true);
const svgRef = useRef();
let chart = null;
let nav = null;
const navRef = useRef();
const isMount = IsFirstRender();
useEffect(
() => {
console.log(selection);
console.log(previousSelection);
chart = new Chart(MARGIN, svgRef, props.data, props.xAxisText, props.yAxisText);
nav = new NavChart(MARGIN, navRef, props.data, previousSelection.current, selection, setSelection);
}, [props.data, previousSelection, selection]
);
return (
<div>
<svg ref = {svgRef} width = {700} height = {400}></svg>
<br /><br/>
<svg ref = {navRef} width = {700} height = {75}></svg>
</div>
);
}
NavChart.js(这是我拥有画笔组件的地方)
import {select,
selectAll,
extent,
axisBottom,
axisLeft,
scaleLinear,
scaleTime,
line,
brushX,
event
} from 'd3';
const PADDING = {TOP: 10, BOTTOM: 30};
export default class NavChart {
constructor(MARGIN, svgRef, data, previousSelection, selection, setSelection) {
this.svg = select(svgRef.current);
this.MARGIN = MARGIN;
this.data = data;
this.chartWidth = this.svg.attr('width') - MARGIN.LEFT - MARGIN.RIGHT;
this.chartHeight = this.svg.attr('height') - PADDING.TOP - PADDING.BOTTOM;
this.xScale = scaleTime()
.domain(extent(
this.data.map((d) => d.time)
))
.range([0, this.chartWidth]);
this.yScale = scaleLinear()
.domain(extent(
this.data.map((d) => d.value)
))
.range([this.chartHeight, 0]);
this.myLine = line()
.x((d) => this.xScale(d.time))
.y((d) => this.yScale(d.value));
this.chartArea = this.svg.append('g')
.attr('transform', `translate(${MARGIN.LEFT}, ${PADDING.TOP})`);
this.chartArea.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', this.chartWidth)
.attr('height', this.chartHeight)
.attr('fill', '#f2f2f2');
this.pathsG = this.chartArea.append('g')
.attr('class', 'pathsG')
.attr('transform', `translate(0,0)`);
this.xAxis = axisBottom(this.xScale);
this.xAxisG = this.chartArea.append('g')
.attr('transform', `translate(0, ${this.chartHeight})`);
this.xAxisG.call(this.xAxis);
this.pathsG.selectAll('path')
.data([this.data])
.join('path')
.attr('d', (value) => this.myLine(value))
.attr('fill', 'none')
.attr('stroke', '#D073BA')
.attr('stroke-width', '1.5');
this.circlesVar = this.pathsG.selectAll('.circle')
.data(data);
this.circlesVar.enter().append('circle')
.attr('class', 'circle')
.attr('r', (value) =>
value.time > selection[0] && value.time <= selection[1] ? 4 : 2)
.style('fill', '#D073BA')
.attr('cx', (d) => this.xScale(d.time))
.attr('cy', (d) => this.yScale(d.value));
this.brushG = this.chartArea.append('g')
.attr('class', 'brush');
this.brush = brushX().extent([
[0, 0],
[this.chartWidth, this.chartHeight]
]).on('start brush end', () => {
if (event.selection) {
const timeSelection = event.selection.map(this.xScale.invert);
setSelection(timeSelection); // This is where I suspect the error is?
}
});
if (previousSelection === selection) {
console.log("true");
this.brushG.call(this.brush)
.call(this.brush.move, selection.map(this.xScale));
}
}
}
UsePrevious.js(用于跟踪变量状态的功能组件)
import {useEffect, useRef} from 'react';
function UsePrevious(value) {
const ref = useRef();
useEffect( () => {
ref.current = value;
});
return ref;
}
export default UsePrevious;
运行代码后,图表在浏览器中可以很好地加载,但是变得非常缓慢,我在控制台中收到此错误-“
StackTrace->
警告:超过最大更新深度。当组件在useEffect中调用setState时会发生这种情况,但useEffect要么没有依赖项数组,要么在每个渲染器上都有一个依赖项更改。
在ChartWrapper中(在App.js:15)
在div中(在App.js:14)
在App中(位于src / index.js:10)
在StrictMode中(在src / index.js:9处)index.js:1
e index.js:1
反应5
-> printWarning
->错误
-> checkForNestedUpdates
-> scheduleUpdateOnFiber
-> dispatchAction
刷NavChart.js:90(这是“ setSelection(timeScale)”行)
套用dispatch.js:61
customEvent on.js:103
发出brush.js:320
开始brush.js:307
移动brush.js:251
默认为each.js:5
移动brush.js:240
默认的call.js:4
NavChart NavChart.js:96(这是“ this.brushG.call(this.brush)”行)
ChartWrapper ChartWrapper.js:29(这行是'nav = new NavChart(MARGIN,navRef,props.data,previousSelection.current,selection,setSelection);')
反应6
-> commitHookEffectListMount
-> commitPassiveHookEffects
-> callCallback
-> invokeGuardedCallbackDev
-> invokeGuardedCallback
-> flushPassiveEffectsImpl
stable_runWithPriority scheduler.development.js:653
反应3
-> runWithPriority $ 1
-> flushPassiveEffects
-> commitBeforeMutationEffects
workLoop scheduler.development.js:597
flushWork scheduler.development.js:552
performWorkUntilDeadline scheduler.development.js:164
现在,我了解到这可能是由于无限循环所致,每次useEffect依赖关系更改时都会渲染我的组件,但是我不知道何时发生。我唯一更改依赖项的地方是在navChart组件内调用setSelection(timeSelection)的行中。另外,该行仅应在更改笔刷时运行。
我在做什么错?
答案 0 :(得分:0)
您应该将依赖关系传递给useEffect,否则它将被称为发生任何更改
import {useEffect, useRef} from 'react';
function UsePrevious(value) {
const ref = useRef();
useEffect( () => {
ref.current = value;
}, [value]);
return ref;
}
export default UsePrevious;
答案 1 :(得分:0)
因此,我找到了解决问题的方法。当我调用“ on()”方法时,在“ NavChart.js”中,if语句为:
if (event.selection)
每次上线时都会调用它
this.brushG.call(this.brush)
被调用。这是因为事件侦听器不仅侦听通过鼠标完成的笔刷更改,而且还侦听以编程方式完成的更改,我想避免这种情况。通过将if语句更改为:
if(event.selection && event.sourceEvent)
您摆脱了这个问题,因为由于编程更改而调用事件时event.sourceEvent为null。仅当事件由于外部事件(例如鼠标移动)而触发时,sourceEvent才会有一个值。