在React的 useEffect中进行动态图表初始化时,无法读取null的属性(读取 'getContext')
我正在尝试在一个React组件中,使用useRef来定位canvas元素并初始化一个Chart.js实例。该组件在挂载时会从API异步获取数据。
如果我用硬编码的静态数据来初始化图表,效果就完全正常。但是,当我在异步API请求完成后再进行初始化时,会抛出致命错误并使应用崩溃。
import { useEffect, useRef, useState } from 'react';
import Chart from 'chart.js/auto';
export default function AnalyticsDashboard() {
const canvasRef = useRef(null);
const [analyticsData, setAnalyticsData] = useState(null);
const [loading, setLoading] = useState(true);
// 1. Fetch data on mount
useEffect(() => {
async function fetchMetrics() {
try {
const response = await fetch('/api/v1/analytics');
const data = await response.json();
setAnalyticsData(data); // e.g., { labels: ['A', 'B'], values: [10, 20] }
} catch (err) {
console.error("Failed to fetch", err);
} finally {
setLoading(false);
}
}
fetchMetrics();
}, []);
// 2. Initialize chart when data arrives
useEffect(() => {
if (!analyticsData) return;
// ERROR THROWN HERE 👇
const ctx = canvasRef.current.getContext('2d');
const chartInstance = new Chart(ctx, {
type: 'bar',
data: {
labels: analyticsData.labels,
datasets: [{ data: analyticsData.values }]
}
});
return () => chartInstance.destroy();
}, [analyticsData]);
if (loading) return <div>Loading metrics...</div>;
return (
<div className="chart-container">
<h2>Monthly Traffic</h2>
<canvas ref={canvasRef} id="trafficChart" width="400" height="200"></canvas>
</div>
);
}
错误控制台输出:
Uncaught TypeError: Cannot read properties of null (reading 'getContext')
at AnalyticsDashboard.js:31:35
at commitHookEffectListMount (react-dom.development.js:23150:26)
at commitPassiveMountOnFiber (react-dom.development.js:24408:13)
我尝试过的做法:
-
我在调用getContext之前加上了if (!canvasRef.current) return;。错误消失了,但图表根本不渲染——canvasRef.current一直保持为null。
-
如果我移除if (loading) return...的提前返回状态,能工作,但页面看起来会很破碎,因为在API加载完成之前,它尝试用空数据渲染标题。
为什么useEffect里面的ref会是null,即使canvas元素在JSX中是明确存在的?我该如何修复这里的时序问题?
解决方案
我没能用一个简单的示例重现,但我猜这与状态更新的时序有关。
目前,你可能会处于一种你没有考虑到的状态:analyticsData 不是 null(或 undeifned),而 loading 仍然是 true。
在这种情况下,由于提前返回你不会渲染画布,但副作用已经尝试访问该ref。
我会用更强的条件来保护第二个副作用:
if (!analyticsData || loading) return;
为了更好地调试时序,你可以将设置ref的函数传入 ref 属性:
<canvas
ref={(node) => {
canvasRef.current = node;
console.log("canvas node:", node);
}}
/>
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。