reactjs - 无法实现 D3.js 以使用 useRef 进行反应。正确的方法是什么?
问题描述
我正在尝试使用 d3.js 创建一个折线图,并将其实现为带有钩子的函数组件。
我尝试使用useRef
. 将它们初始化为 null,然后在 JSX 中设置它们,如您在代码中所见。当我想使用clientWidth
which uses lineChart
ref 它说它是空的。
我想创建一个 initializeDrawing 函数来初始化空分配,但是由于组件不会在变量更改时刷新,所以它不起作用。我也想过携带这些变量,useState
但这似乎不是一个好的解决方案。
我想向您学习使用功能组件实现 d3.js 图表的最佳解决方案是什么。
我正在尝试实现这个示例,但主要目标是学习实现 d3.js 以使用钩子反应功能组件的一般原则。
这是代码:
...
...
...
const LineChart: FunctionComponent = (props) => {
let dataArrIndex = 0;
const lineChart = useRef((null as unknown) as HTMLDivElement);
const xAxis = useRef((null as unknown) as SVGGElement);
const yAxis = useRef((null as unknown) as SVGGElement);
const margin = { top: 40, right: 40, bottom: 40, left: 40 };
const aspectRatio = 9 / 16;
const chartWidth = lineChart.current.clientWidth - margin.left - margin.right;
const chartHeight =
lineChart.current.clientWidth * aspectRatio - margin.top - margin.bottom;
const yScale = d3.scaleLinear().range([chartHeight, 0]);
const xScale = d3.scaleBand().range([0, chartWidth]);
const update = () => {
yScale.domain([
0,
d3.max(
data,
(d): number => {
return d[dataArrIndex].balance;
},
) as number,
]);
xScale.domain(data[dataArrIndex].map((d) => d.month));
d3.select(yAxis.current).call(d3.axisLeft(yScale));
d3.select(xAxis.current).call(d3.axisBottom(xScale));
};
return (
<div className="line-chart" ref={lineChart}>
<svg width={chartWidth} height={chartHeight}>
<g transform={`translate(${margin.left},${margin.bottom})`} />
<g ref={yAxis} />
<g ref={xAxis} />
<path />
</svg>
</div>
);
};
export default LineChart;
解决方案
问题是您尝试在 dom 中创建此元素之前对元素使用 ref,因此您有 null 而不是对 htmlelement 的 ref。要解决您的问题,您需要将所有使用 refs 的代码包装到 useEffect 挂钩。
像这样的东西:
useEffect(() => {
const chartWidth = lineChart.current.clientWidth - margin.left - margin.right;
const chartHeight =
lineChart.current.clientWidth * aspectRatio - margin.top - margin.bottom;
const yScale = d3.scaleLinear().range([chartHeight, 0]);
const xScale = d3.scaleBand().range([0, chartWidth]);
const update = () => {
yScale.domain([
0,
d3.max(
data,
(d): number => {
return d[dataArrIndex].balance;
},
) as number,
]);
xScale.domain(data[dataArrIndex].map((d) => d.month));
d3.select(yAxis.current).call(d3.axisLeft(yScale));
d3.select(xAxis.current).call(d3.axisBottom(xScale));
};
}
使用效果在功能组件中用作 componentDidMount/componentDidUpdate/componentWillUnmount 的类比
如果每次组件接收更新时都需要调用方法“update”,则需要将其从 useEffect 移动到函数体并在 ueEffect 中调用它:
const update = () => {
yScale.domain([
0,
d3.max(
data,
(d): number => {
return d[dataArrIndex].balance;
},
) as number,
]);
xScale.domain(data[dataArrIndex].map((d) => d.month));
d3.select(yAxis.current).call(d3.axisLeft(yScale));
d3.select(xAxis.current).call(d3.axisBottom(xScale));
};
useEffect(() => {
const chartWidth = lineChart.current.clientWidth - margin.left - margin.right;
const chartHeight =
lineChart.current.clientWidth * aspectRatio - margin.top - margin.bottom;
const yScale = d3.scaleLinear().range([chartHeight, 0]);
const xScale = d3.scaleBand().range([0, chartWidth]);
update();
}
您可以通过多种方式使用宽度高度等属性:
使用 useState 钩子将其设置为状态: const { width, setWidth } = useState(0) 并在 useEffect 中从 ref 更新它
检查是否 ref !== null
<svg width={chartRef && chartRef.current.offsetWidth} />
推荐阅读
- azure-devops - 跳过 ci 是否适用于构建验证管道?
- node.js - 在 react native 中根据主题设置动态背景颜色
- google-apps-script - 如果文件的名称以早于当前日期的日期开头,则删除文件(Google Drive)
- python - 使用按钮操作后通过 API 抓取数据
- javascript - 仅使用脚本将重点放在输入上
- python - ValueError:概率包含 NaN
- react-native - React Native Scroll View 弹回顶部
- apache-spark - Spark 结构化流式传输 - 将数据存储到 Azure datalake gen1 时出错
- python - 'SYMBOL' 对象不可调用
- javascript - 在 javascript 中,我如何使用 fs.writeFile 并循环一个数组并在一个新的文本文件中垂直完全打印数组中的每个项目