You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Apache ECharts+TypeScript报错:类型'0'无法索引Arrayable<LineSeriesOption$1>

修复React+Apache ECharts中的TypeScript索引错误

问题场景

在React项目中使用Apache ECharts时,尝试从LineSeriesOption获取数据长度,代码如下:

const dataLen = option.series?.[0].data.length;

触发了TypeScript错误:

[1] src/react_components/e-charts/LineChart.tsx(66,25): error TS7053: Element implicitly has an 'any' type because expression of type '0' can't be used to index type 'Arrayable<LineSeriesOption$1>'.

完整组件代码

import type { LineSeriesOption } from "echarts/charts";
import { LineChart } from "echarts/charts";
import type { GridComponentOption, TitleComponentOption, TooltipComponentOption } from "echarts/components";
import {
    DataZoomComponent,
    GridComponent,
    LegendComponent,
    TitleComponent,
    ToolboxComponent,
    TooltipComponent,
} from "echarts/components";
import type { ComposeOption, ECharts, SetOptionOpts } from "echarts/core";
import { getInstanceByDom, init, use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import { CSSProperties, ReactNode, useEffect, useRef, useState } from "react";

use([
    LegendComponent,
    LineChart,
    GridComponent,
    TooltipComponent,
    TitleComponent,
    ToolboxComponent,
    DataZoomComponent,
    CanvasRenderer,
]);

export type EChartsOption = ComposeOption<
    LineSeriesOption | TitleComponentOption | GridComponentOption | TooltipComponentOption
>;

export interface ReactEChartsProps {
    option: EChartsOption;
    style?: CSSProperties;
    settings?: SetOptionOpts;
}

let currentIndex = -1;

export function EChartLineChart({ option, style }: ReactEChartsProps): ReactNode {
    const chartRef = useRef<HTMLDivElement | null>(null);
    const [chartInitialized, setChartInitialized] = useState(false);

    useEffect(() => {
        let chart: ECharts | undefined;
        if (chartRef.current !== null) {
            chart = init(chartRef.current as HTMLDivElement);
            setChartInitialized(true);
        }

        function resizeChart() {
            chart?.resize();
        }

        window.addEventListener("resize", resizeChart);

        return () => {
            chart?.dispose();
            window.removeEventListener("resize", resizeChart);
        };
    }, []);

    useEffect(() => {
        const canvas = chartRef.current!.querySelector<HTMLCanvasElement>("canvas");
        const chart = getInstanceByDom(chartRef.current as HTMLDivElement);
        const dataLen = option.series?.[0].data.length;

        const handleKeydown = (e: KeyboardEvent) => {
            if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
                chart?.dispatchAction({
                    type: "downplay",
                    seriesIndex: 0,
                    dataIndex: currentIndex,
                });
                currentIndex =
                    e.key === "ArrowRight"
                        ? (currentIndex + 1) % dataLen
                        : currentIndex <= 0
                        ? dataLen - 1
                        : currentIndex - 1;
                chart?.dispatchAction({
                    type: "highlight",
                    seriesIndex: 0,
                    dataIndex: currentIndex,
                });
                chart?.dispatchAction({
                    type: "showTip",
                    seriesIndex: 0,
                    dataIndex: currentIndex,
                });
            }
        };
        const addKeydownListener = () => window.addEventListener("keydown", handleKeydown);
        const removeKeydownListener = () => window.removeEventListener("keydown", handleKeydown);
        canvas?.setAttribute("tabindex", "0");
        canvas?.addEventListener("focusin", addKeydownListener);
        canvas?.addEventListener("focusout", removeKeydownListener);

        return () => {
            window.removeEventListener("keydown", handleKeydown);
            canvas?.removeEventListener("focusin", addKeydownListener);
            canvas?.removeEventListener("focusout", removeKeydownListener);
        };
    }, [chartInitialized]);

    useEffect(() => {
        if (chartRef.current !== null) {
            const chart = getInstanceByDom(chartRef.current as HTMLDivElement);
            chart!.setOption(option);
        }
    }, [option]);

    return <div ref={chartRef} style={{ width: "100%", height: "250px", ...style }} />;
}

错误原因

ComposeOption返回的series类型是Arrayable<LineSeriesOption>,即它可能是单个LineSeriesOption对象,也可能是LineSeriesOption[]数组。直接用[0]索引时,TypeScript无法确定它是数组类型,因此抛出索引错误。

修复方案

方案一:统一转为数组类型(推荐)

先判断series的类型,将单个对象包装为数组,确保后续操作的类型安全性,同时添加空值兜底:

// 处理series,确保是数组格式
const series = Array.isArray(option.series) 
  ? option.series 
  : option.series ? [option.series] : [];
// 获取数据长度,空值时默认0避免运行时错误
const dataLen = series[0]?.data?.length || 0;

方案二:类型断言(谨慎使用)

如果你能100%确定option.series是数组结构,可以用类型断言直接告诉TypeScript:

const dataLen = (option.series as LineSeriesOption[])[0]?.data?.length || 0;

注意:如果series实际是单个对象,这种写法会导致运行时错误,仅在数据结构明确的场景下使用。

额外优化

  1. 完善依赖项:原代码中处理键盘事件的useEffect缺少option依赖,导致option更新时逻辑不会同步,需要将option加入依赖数组:
useEffect(() => {
    // ... 原有代码
}, [chartInitialized, option]);
  1. 全局变量改为组件状态:全局currentIndex会导致多个组件实例互相干扰,建议改为组件内状态:
export function EChartLineChart({ option, style }: ReactEChartsProps): ReactNode {
    // ... 其他状态
    const [currentIndex, setCurrentIndex] = useState(-1);
    
    // 修改handleKeydown中的索引更新逻辑
    const handleKeydown = (e: KeyboardEvent) => {
        if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
            chart?.dispatchAction({
                type: "downplay",
                seriesIndex: 0,
                dataIndex: currentIndex,
            });
            setCurrentIndex(prev => {
                const newIndex = e.key === "ArrowRight"
                    ? (prev + 1) % dataLen
                    : prev <= 0
                    ? dataLen - 1
                    : prev - 1;
                chart?.dispatchAction({
                    type: "highlight",
                    seriesIndex: 0,
                    dataIndex: newIndex,
                });
                chart?.dispatchAction({
                    type: "showTip",
                    seriesIndex: 0,
                    dataIndex: newIndex,
                });
                return newIndex;
            });
        }
    };
}

内容的提问来源于stack exchange,提问作者Leff

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 13:07:11