React Hooks中useEffect获取div为null引发Highcharts报错问题
问题根源
你碰到这个问题和useEffect是否等价于componentDidMount没有直接关系,核心原因有两个:
- 异步函数存在执行时序断层
useEffect的回调本身确实是在首次DOM挂载完成后同步触发的,但你在useEffect里调用的fetchChartData是async异步函数:当函数执行到await fetchChartData(接口请求)这一行时,会暂停当前函数执行,把后续逻辑(console打印、Highcharts初始化)推入微任务队列,等待接口请求完成后再执行。
这段等待接口返回的时间里,DOM完全可能发生变化:最常见的就是React 18开发环境严格模式会自动执行「挂载→卸载→重新挂载」的双调用校验流程,另外父组件条件渲染临时移除当前Chart组件、路由切换导致组件卸载也会触发DOM变动。等接口终于返回时,首次渲染生成的id="data-consumed"节点早就被从DOM树中移除了,此时调用document.getElementById("data-consumed")自然返回null。你描述的「首次打印拿null、后续执行能拿到DOM」的现象,刚好和严格模式的双执行逻辑完全吻合。 - DOM获取方式不符合React设计逻辑
直接通过id全局查找DOM本身就不可靠:如果页面上存在多个同id的元素、组件重渲染后DOM节点被替换,全局查找很容易拿到错误引用或者null,Highcharts抛出的Error #13就是初始化时找不到指定渲染容器的典型报错。
修复方法
按下面的逻辑调整代码即可解决问题:
- 用
useRef绑定渲染容器,代替全局id查找,保证拿到的始终是当前组件实例对应的DOM节点 - 增加组件卸载状态标记,避免组件卸载后、接口返回时再执行Highcharts初始化的无效操作
- 把依赖项正确加入useEffect依赖数组,避免闭包陷阱
修复后的核心代码示例:
import { useEffect, useRef } from 'react'; import Highcharts from 'highcharts'; import moment from 'moment'; const Chart2 = (props) => { // 用ref绑定图表容器 const chartContainerRef = useRef(null); const { fetchChartData } = props; useEffect(() => { // 标记组件是否已卸载,处理异步竞态 let isUnmounted = false; const initChart = async () => { const startDate = moment().startOf("year").toISOString(); const endDate = moment().endOf("year").toISOString(); const data = await fetchChartData({ startDate, endDate, userId: "XXXXXXX" }); // 组件已经卸载、或容器不存在就不再执行后续逻辑 if (isUnmounted || !chartContainerRef.current) return; Highcharts.chart(chartContainerRef.current, { chart: { type: "column", }, title: { text: null, }, subtitle: { text: "Source: WorldClimate.com", }, xAxis: { categories: data.map(({ _id }) => _id), crosshair: true, }, yAxis: { min: 0, title: { text: "Rainfall (mm)", }, }, plotOptions: { column: { pointPadding: -0.1, borderWidth: 0, color: "#5036D6", }, }, series: [ { name: null, data: data.map(({ processingStorage }) => processingStorage), }, ], }); }; initChart(); // 组件卸载时更新标记 return () => { isUnmounted = true; } }, [fetchChartData]); return ( <div className="chart-container cursor-pointer"> <div className="chart-block"> <h4>Data Consumed</h4> {/* 把ref绑定到容器上,移除容易冲突的硬编码id */} <div ref={chartContainerRef} className="graph-block" /> </div> </div> ); }; export default Chart2;
内容的提问来源于stack exchange,提问作者Profer
相关产品推荐
相关产品推荐

