三、兼容性问题:框架集成与运行环境的排雷
3.1 痛点场景
- Next.js / SSR 启动即崩 :
ReferenceError: window is not defined。 - React 18 开发模式图表被画两次:StrictMode 双调用 effect,旧包装器重复 mount。
- 打包后类型报错 / 模块找不到 :
Cannot find module 'highcharts/modules/exporting'。 - 旧浏览器白屏:IE / 老 Safari 直接不渲染。
3.2 原理解析
Highcharts 在 import 阶段就会访问 window / document (用于测量、DOM 适配),而 SSR(服务端渲染)环境没有这些全局对象,所以必须在"客户端"才加载。React 18 的 StrictMode 在开发模式下会把 effect 跑两遍,旧版 highcharts-react-official 没做去重,于是出现重复图表。这些是集成姿势问题,不是库不兼容。
3.3 官方解法与代码
这部分整体方向对,但有几处需要修正,尤其是 SSR、React 版本和模块导入路径。
1. Next.js / SSR:客户端组件不一定等于"所有场景都安全"
在 App Router 中,图表组件应放在客户端组件中:
tsx
'use client';
import React from 'react';
import { Chart, Title } from '@highcharts/react';
import { LineSeries } from '@highcharts/react/series/Line';
export function RevenueChart() {
return (
<Chart>
<Title>Revenue</Title>
<LineSeries data={[1, 3, 2, 4]} />
</Chart>
);
}
如果项目中的某个 Highcharts 模块仍在导入阶段访问 DOM,或使用的是旧包装器,可以使用 next/dynamic 禁用 SSR:
tsx
import dynamic from 'next/dynamic';
const RevenueChart = dynamic(
() => import('./RevenueChart'),
{ ssr: false }
);
export default function Page() {
return <RevenueChart />;
}
注意:dynamic(() => import('@highcharts/react')) 通常不是正确的最终用法,因为需要动态加载并渲染你自己的图表组件,而不是直接把整个包当作页面组件。
2. React 18 StrictMode:不要假定一定会重复绘制
React 开发模式下,StrictMode 可能重复执行挂载和清理流程,用于发现副作用问题。若使用旧版包装器,可能看到重复初始化或销毁异常。
当前建议:
bash
npm install highcharts@^12 @highcharts/react
@highcharts/react v5 的实际 peer 依赖要求是 React >=18,并不要求必须是 React 18.3.1。因此不应把 18.3.1 写成硬性要求。
使用 v5 时,推荐通过组件管理系列和模块:
tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { Chart, Title, XAxis, YAxis } from '@highcharts/react';
import { Accessibility } from '@highcharts/react/modules/Accessibility';
import { LineSeries } from '@highcharts/react/series/Line';
function App() {
return (
<Chart>
<Title>Sales</Title>
<Accessibility />
<XAxis categories={['A', 'B', 'C']} />
<YAxis title="Value" />
<LineSeries data={[1, 3, 2]} />
</Chart>
);
}
createRoot(document.getElementById('container')!).render(<App />);
迁移时不要只做包名替换,还需要调整导入方式:
highcharts-react-official→@highcharts/reactLineSeries→@highcharts/react/series/LineExporting→@highcharts/react/modules/ExportingAccessibility→@highcharts/react/modules/Accessibility
3. 按需导入:不要混用不匹配的模块格式
对 React v5,优先使用集成组件:
tsx
import { Exporting } from '@highcharts/react/modules/Exporting';
import { LineSeries } from '@highcharts/react/series/Line';
对于没有对应 React 组件的 Highcharts 模块,再使用官方 ESM 路径:
tsx
import 'highcharts/es-modules/masters/modules/marker-clusters.src.js';
不建议将以下写法混用:
js
import Highcharts from 'highcharts';
import 'highcharts/es-modules/masters/highcharts.src.js';
通常选择一种入口即可。还要避免把模块注册到一个 Highcharts 实例,却把图表创建在另一个实例上。
此外,旧式:
js
import Exporting from 'highcharts/modules/exporting';
Exporting(Highcharts);
适用于某些原生 JavaScript/旧构建场景,但不是 @highcharts/react v5 的首选写法。React v5 应优先使用 /modules/Exporting 组件。
4. Vite 等打包器的开发环境问题
如果使用 Vite,某些版本在开发模式下可能需要排除 Highcharts 的依赖预构建:
ts
import { defineConfig } from 'vite';
export default defineConfig({
optimizeDeps: {
exclude: ['highcharts']
}
});
这通常只在特定版本组合或开发服务器报模块初始化错误时需要,不应默认添加到所有项目中。
5. TypeScript "找不到模块"的排查顺序
遇到 Cannot find module 时,先检查:
highcharts和@highcharts/react是否已安装。- 包版本是否互相兼容。
- 导入路径大小写是否正确。
- 是否误用了 v4 或旧版路径。
- TypeScript、Bundler 是否解析 ESM。
- 是否存在重复的
highcharts版本。
当前 React v5 常用路径示例:
tsx
import { Chart, Title } from '@highcharts/react';
import { Exporting } from '@highcharts/react/modules/Exporting';
import { LineSeries } from '@highcharts/react/series/Line';
以下路径不应作为 v5 的首选:
tsx
import { Exporting } from '@highcharts/react/options/Exporting';
另外,v5 使用的是 ChartOptions,不是旧的 HighchartsOptionsType:
tsx
import type { ChartOptions } from '@highcharts/react';
const options: ChartOptions = {
chart: {
height: 300
}
};
6. 旧浏览器支持:polyfill 不是全部
Highcharts 的浏览器支持取决于具体版本以及所使用的模块。对于旧浏览器,除了 Promise、fetch 等 polyfill,还可能需要:
- 转译应用代码和依赖;
- 提供
URL、Object.assign等必要 API; - 检查 SVG、ResizeObserver 等浏览器能力;
- 针对导出功能额外部署客户端导出依赖;
- 使用当前 Highcharts 版本文档中声明的浏览器支持范围。
offline-exporting 主要解决是否依赖导出服务器的问题,并不能自动解决旧浏览器的全部兼容性问题。
7. 平行坐标图不要与普通多 Y 轴混淆
如果需求是平行坐标图,应使用专门的配置,例如:
js
Highcharts.chart('container', {
chart: {
parallelCoordinates: true,
parallelAxes: {
lineWidth: 1
}
},
xAxis: {
categories: ['身高', '体重', '年龄']
},
series: [{
data: [180, 75, 32]
}, {
data: [165, 60, 28]
}]
});
chart.parallelAxes 用于提供平行坐标轴的通用配置,但具体的 yAxis 配置优先级更高。它不是普通多 Y 轴布局的替代方案。
参考文档: