开眼界!阿里用这样的组件开发大屏(一)

10,936 阅读5分钟

Hi~ 我是前端学徒业枫,小伙伴们有看前几天的云栖大会直播吗?我已经被“无影”刷屏了...云栖除了云电脑,还有大量的大屏可在线上直接体验,那么在阿里我们是用什么样的组件开发大屏的呢?

我将向大家分享可视化系列文章,介绍我们的前端开源图表库📈 CloudCharts(Github) 如何基于 G2 封装,通过少量配置项即可绘制图表,最终服务内部近两百个产品和许多对外云产品。

我将针对 G2 在大规模使用场景中遇到的问题做详解:

  1. 组件库整体的架构是什么?如何在React中优雅的使用,同时管理好组件内部的代码?

  2. 如何统一约束数据格式?

  3. 如何做到开箱即用,降低使用门槛?

  4. 如何收敛图表主题展现并快速切换主题?

  5. 如何低代码构建图表?

今天是系列第一篇,来讲讲前两个问题~

问题分解

1、组件库整体的架构是什么?如何在React中优雅的使用,同时管理好组件内部的代码?

在 React 中使用 G2 有 BizCharts 和 Viser 等方案,但他们只是做了简单的语法糖,距离真正开箱即用还有一段距离。因此我们要制定一套方案,既能让用户使用简单,又能管理好组件库内部的代码,保证可维护性。

2、如何统一约束数据格式?

G2 的数据映射十分灵活强大,但在实际业务使用中反而会增加学习门槛,降低效率。所以我们将组件的数据格式统一约束为类似 HighCharts 的数据格式,在内部做数据映射。

有了思路,接下来康康具体怎么实现👀 :

1、建立生命周期

首先搭建组件库整体的架构,让项目能长期的维护和发展,尽量减少可能产生的历史包袱。使我们的组件库能在React 中优雅使用,并管理好组件内部代码。

一开始没有好的思路不要紧,我们先把组件绘制图表的全部流程列出来:

观察图表绘制的过程,我们可以将其按照组件生命周期划分为几个部分:创建期、存在期、销毁期。这和 React 的生命周期是基本一致的。

我们发现这些绘制流程很大一部分是可以复用的,大多数组件只有“声明绘制逻辑”这一步不一样,以此建立一套生命周期管理机制。

组件代码由原生代码实现,负责声明 G2 的图形语法。中间的工厂函数负责创建图表实例,调用生命周期,以及一些复用的逻辑,最后返回 React 组件。

工厂函数示例

工厂函数是管理生命周期的核心,在这里简单展示工厂函数的代码。实际逻辑会多很多,有兴趣的同学可以去 Github 查看源码:传送门

import G2 from '@antv/g2';
import React from 'react';

function factory(name, component) {
  class CloudCharts extends React.Component {
    componentDidMount() {
      // 设置初始高宽
      this.initSize();
      // 初始化图表
      this.initChart(this.props);
    }

    componentDidUpdate(prevProps) {
      // 配置项有变化,重新生成图表
      this.rerender();
      // 数据有变化,更新数据
      component.changeData(data);
    }

    componentWillUnmount() {
      this.destroy();
    }

    // 初始化适配高宽
    initSize(props) {}

    // 初始化图表
    initChart(props) {
      this.chart = new G2.Chart({
        container: this.chartDom,
      });
      // 调用组件代码
      component.init(this.chart);
    }

    destroy() {
      component.destroy();
    }

    render() {
      return (
        <div ref={dom => (this.chartDom = dom)} />
      );
    }
  }

  CloudCharts.displayName = `CloudCharts${name}`;

  return CloudCharts;
}

2、数据格式映射

建立起统一的生命周期管理后,下一步要约束用户传入的数据格式。我们在工厂函数中内置一层数据转换,允许用户传入 HighCharts 格式的数据。那么内部是怎么实现的呢?

我们来观察一下这张普通的图表,只由一条折线和点组成:

对应的 G2 数据格式是一个非常简单的 JSON 数组,数组中的每一项对应图表的一个数据点:

const data = [
  { year: '1995', value: 4.9 },
  { year: '1996', value: 6 },
  { year: '1997', value: 7 },
  { year: '1998', value: 9 },
  { year: '1999', value: 13 },
];

传入 G2 后需要指定数据映射(data fields):year * value

而Highcharts格式稍微复杂一些,但是关键内容(数据的数值)都是一致的:

const data = [
  {
    // name 用于区分多组数据
    name: '折线一',
    data: [
      ['1995', 5],
      ['1996', 6],
      ['1997', 7],
      ['1998', 9],
      ['1999', 13],
    ],
  },
];

所以可以通过循环处理转化为G2的格式:

const newData = [];

data.forEach(oneData => {
  const { name: dataName } = oneData;

  oneData.data.forEach((d, i) => {
    const [x, y, ...extra] = d;
    newData.push({
      x,
      y,
      extra,
      type: dataName,
    });
  });
});

这里返回的 xytype 对应了图形语法中的 field

chart.geom().position(['x', 'y']).color('type')

等于图表库内部固定了 data fields,用户可以使用 HighCharts 格式的数据,这也方便了之前使用 HighCharts 的业务迁移到新图表库中。

中场休息的分割线

有了上面的两步,整个图表库的框架已经有了雏形。

本期就到这啦,下回继续分解!

📢 📢 大家对此有什么想法呢?欢迎在评论提出~


✏️ 下期预告

我会着重解答第三个问题

「如何做到开箱即用,降低使用门槛」

原文首发于微信公众号,记得持续关注我们哦~

团队名片