!!!###!!!title=VChart 按需加载源码分析——VisActor/VChart 社区贡献者文档!!!###!!!!!!###!!!description=---title: 13.2 VChart 按需加载源码详解 key words: VisActor,VChart,VTable,VStrory,VMind,VGrammar,VRender,Visualization,Chart,Data,Table,Graph,Gis,LLM--- 基于前文,我们已介绍了 VChart 图表组成和 Tree Shaking 概念。基于这两个前置概念,接下来将详细阐述 VChart 按需加载的具体实现。 !!!###!!!

基于前文,我们已介绍了 VChart 图表组成和 Tree Shaking 概念。基于这两个前置概念,接下来将详细阐述 VChart 按需加载的具体实现。

VChart图表的核心创建流程

Factory

VChart 的按需加载主要依托 Factory 类来达成。该类承担着重要职责,负责对各类图表、系列、组件、图元、Region、布局以及插件进行注册和创建。下面将从注册机制、创建机制以及图表的按需注册这几个方面展开深入分析。

注册机制

Factory 类提供了一系列静态方法,用于注册不同类型的模块。通过这些方法,模块被注册到 Factory 的静态属性中,进而形成一个注册表。具体代码如下:

static registerChart(key: string, chart: IChartConstructor) {
  Factory._charts[key] = chart;
}
static registerSeries(key: string, series: ISeriesConstructor) {
  Factory._series[key] = series;
}
static registerComponent(key: string, cmp: IComponentConstructor, alwaysCheck?: boolean) {
  Factory._components[key] = { cmp, alwaysCheck };
}
// 其他注册方法...    

创建机制

Factory 类同样提供了一系列静态方法,用于按需创建模块实例。这些方法会依据类型从注册表中查找对应的构造函数,并创建实例。示例代码如下:

static createChart(chartType: string, spec: any, options: IChartOption): IChart | null {
  if (!Factory._charts[chartType]) {
    return null;
  }
  const ChartConstructor = Factory._charts[chartType];
  return new ChartConstructor(spec, options);
}
static createSeries(seriesType: string, spec: any, options: ISeriesOption) {
  if (!Factory._series[seriesType]) {
    return null;
  }
  const SeriesConstructor = Factory._series[seriesType];
  return new SeriesConstructor(spec, options);
}
// 其他创建方法...    

图表的按需注册

以线图为例,下面来看具体的按需注册实现。在 packages/vchart/src/chart/line/line.ts 中,有如下代码:

export const registerLineChart = () => {
  registerLineSeries();
  Factory.registerChart(LineChart.type, LineChart);
};    

其中 registerLineSeriespackages/vchart/src/series/line/line.ts 中实现,代码如下:

export const registerLineSeries = () => {
  registerSampleTransform();
  registerMarkOverlapTransform();
  registerLineMark();
  registerSymbolMark();
  registerLineAnimation();
  registerScaleInOutAnimation();
  registerCartesianBandAxis();
  registerCartesianLinearAxis();
  Factory.registerSeries(LineSeries.type, LineSeries);
};    

通过上述代码实现,在使用 VChart 时,若调用 registerLineChart 注册线图,就会将线图依赖的线的系列、线图元、点图元等必要元素默认注册。具体使用方式如下:

import { VChart } from './core';
import { registerLineChart } from './chart/line';
VChart.useRegisters([
  registerLineChart,
  // 其他需要的模块
]);    

而在 packages/vchart/src/core/vchart.ts 中,创建图表实例时并非直接通过路径引用,而是借助 Factory.createChart 来创建。如此一来,核心类 vchart 就能在不引用所有图表实现的情况下,依据用户注册的图表创建实例。相关代码如下:

private _initChart(spec: any) {
   // ...
    const chart = Factory.createChart(spec.type, spec, this._getChartOption(spec.type));
   //...
  }    

核心类 VChart 的按需加载

接下来探讨一个问题:以下两种引用 VChart 的方式是否存在差异?

  • 方法一:
import VChart from '@visactor/vchart';    

  • 方法二:
import { VChart } from '@visactor/vchart';    

答案是肯定的,这两种引用方式所产生的效果不同。下面分析其差异原因。

首先,在 vchart 代码库对应的 package.json 中,可看到如下配置:

{
  "sideEffects": [
    "./*/index-lark.js",
    "./*/index-wx-simple.js",
    "./*/index-wx.js",
    "./*/vchart-all.js",
    "./*/vchart-simple.js"
  ],
}    

该配置明确声明了所有有副作用的文件。

packages/vchart/index.ts 中,有以下导出内容:

import { VChart } from './vchart-all';
export default VChart;
export * from './core';    

其中 core/index.ts 文件中又有如下导出:

import { VChart } from './vchart';
export { VChart, Factory };    

所以方法一等价于如下引用,引用的 VChart 是 vchart - all 导出的 VChart 类,方法二引用的相当于从 core/vchart.ts 导出的 VChart 类。

import { default as VChart } from '@visactor/vchart';    

其中文件 vchart-all.ts 的主要作用是注册和导出 VChart 的所有功能模块:

VChart.useRegisters([
  // charts
  registerLineChart,
  registerAreaChart,
  // ...其他图表
  // components
  registerCartesianLinearAxis,
  registerCartesianBandAxis,
  // ...其他组件
  // layout
  registerGridLayout,
  registerLayout3d,
  // mark
  registerAllMarks,
  // plugin
  registerDomTooltipHandler,
  registerCanvasTooltipHandler,
  // ...其他插件
]);
export { VChart };    

由于 vchart - all 是声明了有副作用的文件,所以当采用方案一引用 VChart 时,会将所有 vchart-all 中依赖的文件都进行打包;而使用方案二引用 VChart 时,仅会打包 core/vchart.ts

本文档由以下人员修正整理

玄魂