移动端可视化引擎 F2 架构设计之: 为什么要选用 JSX

本文涉及的产品
云原生数据库 PolarDB 分布式版,标准版 2核8GB
简介: 移动端可视化引擎 F2 架构设计之: 为什么要选用 JSX

🙋🏻‍♀️ 编者按:本文作者是蚂蚁集团前端工程师索丘,和大家一起聊一聊为什么移动端可视化引擎 F2 要选用 JSX ,这背后的思考是什么?欢迎查阅~

  从图形语法说起

《图形语法》是 F2 数据可视化的核心理论,而图形语法的核心是通过一套抽象的语法来描述任意的统计图表,根据书中的描述,统计图的定义依靠以下几个基础语法:

声明 描述
DATA 从数据集生成视觉编码的数据操作
TRANS 变量转换(比如:rank)
SCALE 度量转换(比如:log)
COORD 定义坐标系(比如:极坐标)
ELEMENT 图形(比如:点)和他们的美学属性(比如:color)
GUIDE 辅助元素(比如:坐标轴,legend)

如果我们用一份数据结构去表达的话,那么可以抽象成如下的数据结构

{
  data: [
    {"category": 1, "value": 4},
    {"category": 2, "value": 6},
    {"category": 3, "value": 10},
    {"category": 4, "value": 3},
    {"category": 5, "value": 7},
    {"category": 6, "value": 8}
  ],
  trans: {
    value: 'rank',
  },
  scale: {
    value: 'linear',
  },
  coord: 'polar',
  element: {
    "theta": {"field": "category"},
    "radius": {"field": "value"},
    "color": {"field": "category"}
  },
  guide: {
    axis: {
     value: {},
    }
  }   
}

下面来看 F2 3.x API 命令式的描述

chart.data([
  {"category": 1, "value": 4},
  {"category": 2, "value": 6},
  {"category": 3, "value": 10},
  {"category": 4, "value": 3},
  {"category": 5, "value": 7},
  {"category": 6, "value": 8}
]);
chart.scale({
  value: 'linear',
});
chart.coord('polar');
chart.interval('category*value').color('category');
chart.axis({
  value: {},
});
chart.render();

从类型上看我们可以把前面的 JSON 描述归类到「声明式」,API 调用归类到 「命令式」,虽然从最后的结果看没有太大区别,但是从灵活性的角度看,下面的这种 API 命令式要远比 JSON 声明式灵活很多,因为我们可以在调用 API 的过程中加很多逻辑代码,来满足我们的个性化业务诉求,这个也是最早 F2 不直接使用 JSON 的一个原因。

虽然 F2 看起来是命令式的,但是实际上在内部,我们也构造了一个结构对象,用来保存图形语法的描述对象,只是对外提供的是 API 命令式的编程模式,也就是说我们最终的目的都是在构造图形语法的结构描述。

那为什么现在要采用 JSX 而不是沿用之前的 API 命令式呢, 在回答这个问题之前,我们先来看看 JSX 和 JSX 的特点。

  什么是 JSX

什么是 JSX, 可以看 React 官网的 JSX 简介,但是不要把 JSX 和 React 等同起来,JSX 可以让我们方便地创建数据结构,它可以和 React 完全没有关系,我们只要通过 Babel jsx transform 定义自己构造函数。

举个例子:假设我们定义了如下的构造函数和 JSX 结构

export function jsx(type, config, key?: string) {
  return {
    key,
    type,
    ...config,
  };
}
<chart
  data={[
    {"category": 1, "value": 4},
    {"category": 2, "value": 6},
    {"category": 3, "value": 10},
    {"category": 4, "value": 3},
    {"category": 5, "value": 7},
    {"category": 6, "value": 8}
  ]}
  scale={{
    value: 'linear',
  }}
  coord='polar'
>
  <interval position="x*y" />
  <axis field="x" />
</chart>

那么转换后的代码就会变成

import { jsx as _jsx } from "./jsx-runtime";
_jsx('chart', {
  data: [],
  coord: 'polar',
  children: [
    _jsx('interval', {
      position: "x*y",
    }),
    _jsx("axis", {
      field: "x",
    }),
  ],
});

执行完后,就会得到如下的结构

{
  type: "chart",
  data: [],
  coord: "polar",
  children: [
    { type: "interval", position: "x*y" },
    { type: "axis",  field: "x" }
  ]
}

通过 JSX 我们也得到了一个类似的数据结构,所以我们也能利用 JSX 来生成我们需要的结构描述,那么相比之下 JSX 有哪些优势呢,我们再来看看 JSX 的优势。

  JSX 的优势

通过前面我看到,我们最终的目的都是为了得到最后的图形语法结构描述,不管是 JSON, API 命令式,还是 JSX 都是为了生成这份结构,那么 JSX 相比之下又具有哪些优点呢?

1. 可编程性

JSX 可以在结构中很方便地嵌入表达式,这个是 JSON  不具备的能力

举个例子

<chart
  data={[
    {"category": 1, "value": 4},
    {"category": 2, "value": 6},
    {"category": 3, "value": 10},
    {"category": 4, "value": 3},
    {"category": 5, "value": 7},
    {"category": 6, "value": 8}
  ]}
  scale={{
    value: 'linear',
  }}
  coord='polar'
  >
  <interval position="x*y" />
  {
    // 类似这样的表达式在 JSON 中是不好描述的
    showAxis ? <axis field="x" formatter={v => v.toFixed(2)}/> : null
  }
</chart>

从上面的例子我们看到 2 个点:

  1. axis 需要通过 showAxis 这个变量来控制是否显示,如果是 JSON 的话,那么就需要 2 份 JSON 描述
  2. formatter 有自定义的格式化的诉求,而 JSON 无法保存方法的

2. 更强的扩展能力

因为 JSON 需要有配套的 Runtime 来处理 JSON 结构,如果需要对 JSON 进行扩展,那么相应的 Runtime 也需要同步升级,这个在多变的业务场景中带来的成本无疑是巨大的,而 JSX 可以通过扩展标签类型来方便地实现

例子:

import { Custom } from './custom';
<chart
  data={[
    {"category": 1, "value": 4},
    {"category": 2, "value": 6},
    {"category": 3, "value": 10},
    {"category": 4, "value": 3},
    {"category": 5, "value": 7},
    {"category": 6, "value": 8}
  ]}
  scale={{
    value: 'linear',
  }}
  coord='polar'
  >
  <interval position="x*y" />
  <axis field="x" />
  { /* 自定义的标签扩展 */ }
  <Custom ... />
</chart>

JSX 里有个默认规则,小写是内置标签(对应的类型为string),大写的是外部引用(可以是任意类型),所以只要约定 Custom 对应的接口(在 React 里就是 Component),就能实现无限扩展,但是这样也会带来一个问题,那就是生成的结构不再能被序列化传输和存储,这个我们后面的篇幅再讨论。

3. 更稳定的树结构

要理解这一点需要对 JSX 生成的结构有更深的理解,我这里只简单提一下,大家有兴趣可以去研究下 JSX 的编译规则 这句话可以这么理解:「不管外部参数如何变化,JSX 返回的结构树是稳定的」,稳定的结构树,是后续 diff 的基础。

例子:

<chart
  data={[
    {"category": 1, "value": 4},
    {"category": 2, "value": 6},
    {"category": 3, "value": 10},
    {"category": 4, "value": 3},
    {"category": 5, "value": 7},
    {"category": 6, "value": 8}
  ]}
  scale={{
    value: 'linear',
  }}
  coord='polar'
  >
  <interval position="x*y" />
  {
    showAxis ? <axis field="x" /> : null
  }
</chart>

我们还是拿这段代码为例,showAxis 为 true 和 false 时,生成的结构对象分别如下所示,这 2 颗树的结构是一致的,并不会因为参数不同而不同。

{
  type: "chart",
  data: [],
  coord: "polar",
  children: [
    { type: "interval", position: "x*y" },
    { type: "axis",  field: "x" }
  ]
}
{
  type: "chart",
  data: [],
  coord: "polar",
  children: [
    { type: "interval", position: "x*y" },
    null,   // 树的节点里还是会保留null
  ]
}

4. 完整的结构描述

JSX 保留了 JSON 结构化描述的特点,这个是 API 命令式不具备的,命令式需要执行完全部代码之后才能得到完整的结构,而且通过转换函数,可以将 JSX 方便地转成 JSON,在面向未来的 nocode、lowcode、甚至是智能化场景,JSON 无疑是一种最好的形式,而且 JSON 机器友好度比较高。

5. 成熟的配套工具

不管是 Babel 还是 TypeScript 都有成熟的 JSX 编译插件,而且配置简单,详情可看 F2 官网的 jsx-transform

6. 小结

JSX 保留了 JSON 结构化描述的特点,,但相比 JSON 又具备更强的灵活性和可编程性,这些在我们面临复杂的业务场景时是很重要的,但是也因为灵活,所以 JSX 的结构是不可被序列化传输和存储的,这个也是 JSX 的局限,但是这个局限我们可以通过上层更进一步的领域解决方案来封装和解决。

  JSX 之下的 JSON 化

前面我们也提到 JSON 有非常好的机器友好度,尤其是面向搭建和智能化场景,这些场景都需要跨端,甚至是跨平台来传输和存储,所以在面向机器友好度的角度来看,我们还是要对 JSX 进行 JSON 化,前面我们也提到过,序列化传输和存储的问题要通过上层的解决方案来解决,那么我们要如何解决 JSON 化的问题呢?

我们就拿上面这个例子的 formatter举例

<chart
  data={[
    {"category": 1, "value": 4},
    {"category": 2, "value": 6},
    {"category": 3, "value": 10},
    {"category": 4, "value": 3},
    {"category": 5, "value": 7},
    {"category": 6, "value": 8}
  ]}
  scale={{
    value: 'linear',
  }}
  coord='polar'
  >
  <interval position="x*y" />
  {
    // 类似这样的表达式在 JSON 中是不好描述的
    showAxis ? <axis field="x" formatter={v => v.toFixed(2)} /> : null
  }
</chart>

这个例子中,当我们面向所有业务场景时 formatter 的格式是各种各样的,比如保留小数点后 0 - n 位,百分比,保留正负号,日期格式等等,因为我们无法穷举,所以通过函数来处理,但是又因为用了函数,这个结构体就不能被序列化了,这个也是我们前面提到 JSX 不可被序列化的原因。

但是,当我们把业务场景局限到某个特定领域时,这个场景的特点和诉求就能被枚举出来,所以当我们面向某个特定领域时,formatter 的格式也是可能被枚举出来,比如某个场景小数点统一保留 2 位,需要百分比,日期处理等等这些具体的规则,这个时候我们就可以定义 formatter 的类型为 'toFixed' | 'percent' | 'date' 等。

一旦可枚举之后,那么我们就可以用如下的方式来表达了

<chart
  data={[
    {"category": 1, "value": 4},
    {"category": 2, "value": 6},
    {"category": 3, "value": 10},
    {"category": 4, "value": 3},
    {"category": 5, "value": 7},
    {"category": 6, "value": 8}
  ]}
  scale={{
    value: 'linear',
  }}
  coord='polar'
  >
  <interval position="x*y" />
  <customAxis visible={ true } field="x" formatter="toFixed" />
</chart>

而这个结构,就完全可以用下面的 JSON 来描述了

{
  type: "chart",
  data: [],
  coord: "polar",
  children: [
    { type: "interval", position: "x*y" },
    { type: "customAxis",  field: "x", visible: true, formatter: 'toFixed' }
  ]
}

这个 JSON 是完全可以被序列化的,而这个转换在代码上仅仅只是对  axis 进行了再次的封装,而这个也是我们提供给业务,让业务可以做领域二次封装的能力,业务的二次封装还能带来业务使用的便利性。

所以,JSX 不仅能有清晰的结构描述,还有良好的编程能力和扩展性,而且通过二次封装,我们还能 JSON 序列化,所以 JSX 的形式无疑是当下最好的选择

  领域解决方案

我们再来聊聊领域解决方案,我们面向的是整个移动端的数据可视化场景,因为需要考虑通用性和灵活性,它的易用性往往是不够的,还是拿我们前面提到的这个 formatter 举例,在面向特定领域时,格式化的差异会非常大。比如金融类的股票、基金场景:formatter 不仅需要格式化数字,还需要根据数字的 正、负、零来显示不同的颜色(俗称红涨绿跌平盘色),而且美股颜色还需要相反(绿涨红跌),如果业务中这些统一的规则要反复处理,那估计是要发疯的,所以就需要领域解决方案来解决这些问题。

这里顺便再剧透一下,在不远的将来,我们还会在 F2, F6 之上,主要从易用性的角度来考虑,并给大家带来更简单易用的移动端可视化解决方案 FCharts,让普通场景使用简单的同时,也让领域解决方案变得更加方便和易用,敬请期待。

  最后

总结下来,我们选用 JSX 就是看中 JSX 的可编程性、易扩展性、完整的结构描述和成熟的配套,不仅是当下对编程友好度、业务复杂度的考虑,还有未来面向搭建和智能化友好的 JSON, 都是一种很好的选择。

最后,如果想了解更多细节欢迎 star 我们的 GitHub 和 官网

  • F2 GitHub
  • F2 官网
  • F6 GitHub
  • F6 官网


相关实践学习
快速体验PolarDB开源数据库
本实验环境已内置PostgreSQL数据库以及PolarDB开源数据库:PolarDB PostgreSQL版和PolarDB分布式版,支持一键拉起使用,方便各位开发者学习使用。
相关文章
|
3月前
|
运维 Cloud Native 安全
云原生技术:重塑现代IT架构的引擎
在当今数字化时代,企业正面临着前所未有的挑战与机遇。随着云计算技术的不断发展,云原生技术作为其核心驱动力之一,正在彻底改变企业的IT架构和运营模式。本文将深入探讨云原生技术的内涵、特点及其对企业数字化转型的影响,揭示其在现代IT架构中的核心地位和作用。同时,我们还将分析云原生技术面临的安全挑战,并展望未来的发展趋势,为企业在云原生领域的实践提供有益的参考。
|
2月前
|
分布式计算 大数据 Serverless
云栖实录 | 开源大数据全面升级:Native 核心引擎、Serverless 化、湖仓架构引领云上大数据发展
在2024云栖大会开源大数据专场上,阿里云宣布推出实时计算Flink产品的新一代向量化流计算引擎Flash,该引擎100%兼容Apache Flink标准,性能提升5-10倍,助力企业降本增效。此外,EMR Serverless Spark产品启动商业化,提供全托管Serverless服务,性能提升300%,并支持弹性伸缩与按量付费。七猫免费小说也分享了其在云上数据仓库治理的成功实践。其次 Flink Forward Asia 2024 将于11月在上海举行,欢迎报名参加。
209 6
云栖实录 | 开源大数据全面升级:Native 核心引擎、Serverless 化、湖仓架构引领云上大数据发展
|
6月前
|
存储 关系型数据库 MySQL
MySQL数据库进阶第六篇(InnoDB引擎架构,事务原理,MVCC)
MySQL数据库进阶第六篇(InnoDB引擎架构,事务原理,MVCC)
|
6月前
|
存储 Cloud Native 持续交付
云原生架构:未来软件开发的引擎
【6月更文挑战第13天】随着企业数字化转型的加速,云原生技术已成为推动现代软件交付和运维的关键力量。本文将深入探讨云原生架构的核心概念、优势以及它如何重塑软件开发流程,为企业带来前所未有的敏捷性、可扩展性和成本效率。
208 1
|
2月前
|
运维 Cloud Native 安全
云原生架构:企业数字化转型的新引擎##
【10月更文挑战第2天】 在当今数字化浪潮中,云原生架构以其独特的优势成为企业实现高效、灵活和创新的核心驱动力。本文深入探讨了云原生的概念、核心技术如容器化、微服务和DevOps等,并分析了这些技术如何共同作用,推动企业在云平台上实现快速迭代、弹性扩展和资源优化。同时,文章还阐述了云原生在实际应用中面临的挑战及相应的解决策略,为企业的数字化转型提供全面而深入的指导。 ##
54 17
|
2月前
|
运维 Cloud Native 持续交付
探索云原生架构:企业数字化转型的新引擎
在当今数字化浪潮中,云原生架构以其独特的优势成为企业转型的关键。它通过容器化、微服务、DevOps和持续交付等技术,使企业能够快速响应市场变化,实现应用的高效开发、部署和运维。本文将深入探讨云原生的概念、核心技术及其在现代IT环境中的重要性。
|
2月前
|
Kubernetes 监控 Cloud Native
探索云原生架构:企业数字化转型的新引擎
【10月更文挑战第5天】 在当今数字化浪潮中,云原生架构以其独特的优势成为企业实现高效、灵活和可扩展的关键。本文将深入探讨云原生的核心概念、关键技术以及实际应用案例,揭示其在推动企业数字化转型中的重要作用。
39 6
|
2月前
|
运维 Kubernetes Cloud Native
探索云原生架构:企业数字化转型的新引擎
【10月更文挑战第9天】 在当今数字化浪潮中,云原生架构以其独特的优势成为企业实现高效运营和快速创新的关键。本文将深入探讨云原生的核心概念、关键技术以及实际应用案例,揭示其如何助力企业加速数字化转型步伐。通过对云原生技术的剖析,我们将看到这一新兴架构是如何重新定义软件开发、部署和运维模式的,进而推动企业在激烈的市场竞争中脱颖而出。
|
2月前
|
监控 Cloud Native 持续交付
云原生架构:企业数字化转型的新引擎##
在当今数字化浪潮中,云原生架构正成为推动企业创新和竞争力的关键因素。本文探讨了云原生的核心概念、技术优势以及在现代业务场景中的应用实践,揭示了其如何助力企业实现高效运营、灵活扩展与持续集成。通过对云原生技术的深入剖析,我们将看到它不仅是一种技术趋势,更是企业数字化转型的战略选择。 ##
41 5
|
3月前
|
运维 Cloud Native Devops
探究云原生架构:企业数字化转型的新引擎
在当今数字化浪潮中,云原生技术以其独特的优势,正成为推动企业IT变革的重要力量。它不仅能够提升企业的开发效率和业务灵活性,还能帮助企业更好地应对复杂多变的市场环境。本文将深入探讨云原生的核心概念、关键技术以及实际应用价值,旨在为读者提供一个全面而清晰的云原生技术视角。