DataWind 可视化查询数据流重构

发表于 1年以前  | 总阅读数:319 次

背景

DataWind 使用 umi 脚手架,所以数据流一直绑在 dva 方案。由于 dva 本身语法较为陈旧,加上 DataWind 使用的比较粗旷,导致项目拆包时遇到模块间紧紧咬合的问题,牵一发而动全身。

上个双月正好在做模块架构升级,正好把数据流一并升级到新方案,同时解决可视化查询模块内、与其它模块间数据流使用不规范问题,也带来更好的开发体验。

问题

繁琐的初始化模版

排除掉具体内容,初始化数据流的模版代码非常繁琐:

export const getInitialState = (): IState => {...}

const model = combineModel({...}, analysisModels, dynamicFieldModels)

const undoableActionTypes = [...]

function withCancelable<T>(effect: [T, any]): [T, any]
function withCancelable<T>(effect: T): T
function withCancelable(effect) {...}

model.effects.init = withCancelable(model.effects.init)

function vizQueryEnhance<T extends IState>(model: Model): Model {
  const { namespace, effects, reducers } = model
  const modelActionTypes = Object.keys({...})
  const enhancedReducer = getUndoEnhancer<T>(model as any, {...} as UndoableOptions<T>)
  return {...}
}

const { dispatchAction, getLoading, putAction } = getModuleInfo(...)

const enhancedModel = vizQueryEnhance(model as any)
export default enhancedModel

export const myModel = {
  action: dispatchAction,
  innerAction: putAction,
  getLoading,
  getState: getModuleGetter<IMyModelState>(namespace)
}

异步语法老旧

这里不是说 async/await 就比 generator 好,但 async/await 在大部分情况都满足需求,而不需要额外的语法和库支持。实际上,整个可视化查询数据流迁移完后发现,100% 的异步需求都被 async/await 覆盖了,事实胜于雄辩。

同时,在数据流内调用异步函数得使用 cmd.call(fn, args) 的语法,获取数据流的值要使用 cmd.select<IReduxState>(state => ..) 的语法,无疑都不符合简单清晰的直觉,同时要手动传入泛型也显得多此一举。

Effect 调用 reducer 繁琐

因为 reducer 仅支持同步,干净无副作用,所以 Effect 就被拓展出来干脏活。但调用的过于繁琐:

还有字符串这种不利于查找引用、没有类型提示的调用方式:

使用数据流方式繁琐

数据流调用是问题的核心,即使数据流写的再烂,用起来舒服也能把问题解决大半。但实际上存量代码里用的并不舒服,大部分采用 classComponent 的 connect 方法,需要手动申明类型。

整个代码里只有三处地方用到 useStore 且类型都是 any,这会引发另一个问题,后面再说。

调用 action 也不是这么自然,需要为每个组件申明 dispatch 属性,且调用时必须引用到具体 model 才能 . 出具体 function:

然而上面的代码还算是 ts 引用优化后的版本,在优化前,甚至是字符串调用,查找 reference 时根本不 work,必须用精妙的全局搜索才能搜到,而且要注意搜索范围与关键字命名,实在让人疯狂。

用到了就 connect

这句话用无奈的语气念出来。

无论用任何变量,都要 connect 才能拿到,似乎 connect 是唯一获取变量的方法。

其实应该用 store.getState() 获取瞬时值,否则会带来无意义的重渲染,同时如果是 functionComponent,也会让函数无意义的重新实例化。

类似的例子还有很多,几乎所有代码都写错了,不止可视化查询,还包括旧版仪表盘,随便找个例子:

说到这,希望大家能对 react 重拾信心,对不可变数据重拾信心。出现性能问题,先问自己是不是用的不对,再怀疑是不是不可变数据这个模式,这个方向走错了。

应用间耦合

这个问题分两部分看,首先是跨模块引用痛苦。下面是数字大屏为了复用可视化查询数据集选择组件时,需要付出的代价:

看上去八杆子打不着边,但就必须要引用。因为数据集选择组件使用了 dva 全局数据流的 legacyDataSet 模块,不引用这个模块,这个组件就跑不起来。But,为什么要知道这个?

另一个是对 dva 的强依赖,即依赖的模块不去 dva,就去不了 dva,陷入死循环。

由于全局所有公共数据、仪表盘、可视化查询、大屏、数据集、数据问答等等都放在一个大 dva 里,所以一个模块基本别想自己单独去 dva,所有模块一起去,这个工程永远也不可能启动:

从数据流层面,各引用绑在一起,如果要拆分子应用,各应用希望独立升级数据流方案都做不到。数据流包在所有应用之上,而不是在应用内,导致应用拆分时,必定受到数据流的阻挠。

全局唯一实例的问题

下面是一段充满了无奈的代码:

我必须强调多应用实例的思想实验,即就算当前应用只有一个页面,也要幻想一下,同时渲染两个页面会不会出问题,数据流设计是否能跟上生命周期?数据流的调用是否与应用实例相关联?

全局 g_app._store 打破了这个幻想,且不说没有类型,无法应对应用多实例问题,这个写法会导致逻辑调用链的错乱。

比如 A 模块依赖 B 模块,现在 “得益于” window 的状态管理,A 和 B 都可以相互调用了,这看上去是更灵活了,但等梳理逻辑时,会让人头皮发麻。

令我惊讶的是,这个秘诀还是挺广为人知的,很受欢迎:

虽然看了下大部分引用逻辑都没问题,但不可能说所有逻辑都是正确的,而且一眼还看不出来是否出现了不正确的调用关系。

OpenAPI 与应用关系倒置

“得益于” dva 数据流的全局地位,开放 API 也不得不因为 ROI 考虑,优先与 dva 做对接。

在应用渲染前,开放 API 就 work 了,这着实令人费解,因为开放 API 的能力应该全部来自于应用提供,为什么可以提前呢?“得益于” dva。

这看似一个小关系倒置,其实蛮影响维护成本的,为什么?OpenAPI 与应用在这套架构下就是平行关系了,如果我是 dva 小二,要同时给 OpenAPI 和应用端茶倒水,哪位爷没伺候好服务都得挂。

另一方面,直接依赖 dva 也容易导致代码实现变形,即直接依赖 dva 的 getState()、dispatch 做一些事情,其实 OpenAPI 应该不关心实现,只与应用约定接口,应用哪怕用 JQuery 写,用 window 做数据流也不妨碍 OpenAPI 的对接,所以一开始开放就不应关心数据流是什么,而是约定接口。

什么样的数据流是足够好的?

这个问题没有标准答案,但普遍认可的方向是以下几条:

  1. 概念少。最好不要有独创语法或规则。
  2. 强类型。最好不要有什么手段可以绕过类型系统写代码。
  3. 使用方便。最好调用函数只有一行,不要有五花八门的调用方式。
  4. 实现简单。就像 redux 一样实现简单才不容易出岔子。
  5. 支持副作用。仅支持 reducer 肯定不够,但别因此搞一堆 redux 中间件,头疼。
  6. 多实例抗干扰。可以多个数据流实例同时使用,而不会相互干扰。
  7. 可同时用于组件和项目。最好能伸能缩,复杂组件有时候也用得上数据流。

我们内部的数据产品搭建框架提供的数据流能力(底层是 @dp/hookstore 能力[1])就是尽力符合以上几点去做的,以下是我的几个思考:

  1. Action 部分利用 hooks 语法,除了与 react 框架绑定外,几乎没有新增概念。
  2. Typescript 泛型、重载能力足以支持大部分类型推导语法,除了 Partial Type Argument Inference[2]。
  3. 将 useSelector 与 store.getState 合并为一个函数。
  4. 本身基于 react-redux + context + hooks 实现,源码一共 300+ 行。
  5. Hooks 本身支持副作用,无需实现,且对 react 开发者来说 0 学习成本。
  6. 使用 react-redux 的 createSelectorHook 实现多实例间互不干扰。
  7. 由于 4、5、6 的技术选型本身就可以同时用在组件与项目上。

解法

新的数据流方案基于 redux + hook,所以称为 hookStore,即基于 hook 方案的数据流。

初始化模版

首先引用 @dp/wind 包中两个 create 方法,分别创建数据流中间件与数据流:

import { createMiddleware, createWind } from '@dp/wind'

这是图表数据流插件,可以独立使用,也可以插入到业务数据流里使用,利用 createWind 组装这些中间件:

<VizQuery``Wind``/>标签包裹应用,就创造了一个数据流作用域,而这个 < VizQuery``Wind``/> 会写在每个模块内部,比如可视化查询 UI 里才会调用 < VizQuery``Wind``/>,所以即便实例化多套可视化查询应用,也可以同时跑起来。

useVizQuery 是 UI 组件使用数据流的方式,同时组合了获取变量与调用函数,具体用法放后面说。而且通过这种方式创建数据流,ProvideruseState 是一一对应关系,不同 createWind 之间的数据可以叠加使用,不会串。(背后使用了 react-redux 新版 API createSelectorHook实现)

用 hooks 写 Action

不同于 dva,这个数据流方案不区分 reducer 和 effects,createMiddleware 第二个参数是个回调 Hook 函数,在里面可以使用任意 React Hooks 语法,所以不仅可以用来写 Action,也可以用来写组件生命周期相关的逻辑,所以异步也很自然的可以采用 async/await。

统一调用变量与方法

与 dva 甚至大部分现代数据流方案不同,这里只需要 useVizQuery 一个函数就可以同时获取数据、调用方法:

也可以同时拿到变量与方法,变量要通过 selector 获取,当改变时,当前组件会重渲染,而引用本身就是静态的方法不需要任何参数,且不会导致任何重渲染:

不需要泛型、不需要 connect、mapStateToProps、dispatch,也不需要区分拿变量还是方法,useVizQuery 记住这一个方法就可以了。同时 ts 类型也是自动推导,点击引用会直接跳转到对应中间件,中间件内查找 reference 可以找到使用处,且没有给业务字符串调用的口子,方便管理。

应对部分 ClassComponent 文件

大量 ClassComponent 无法使用 hooks 也是一个阻碍,重构时,简单的组件就改成 FunctionComponent,复杂的组件就包裹一层 FCWrapper。其实重构为 FunctionComponent 后代码清晰度得到了巨大的提升,下面是修改前:

上图还漏了个文件末尾的 Connect(DerviedPillMenu),然而修改后简洁了不少:

内置提供获取瞬时值函数

使用原生 react-redux 可以采用 useStore 方式在回调函数里获取瞬时值,但需要自定义一个绑定类型的 useStore

const useStore = reduxUseStore as () => Store<IReduxStore>

function App() {
  const store = useStore()

  const onClick = useCallback(() => {
    console.log(store.getState().userName)
  }, [])
}

在 wind 方案中,调用方式如下:

只要记住 useVizQuery 就行了,不需要额外调用 useStore 方法。

应用内的数据流

改造后的数据流实例化在应用内,且应用如果生成了多份实例,数据流也会相应生成互不干扰的多份实例。最重要的是,可视化查询是仪表盘的基础,会整体作为一个组件被仪表盘调用,那么在这个数据流方案下,仪表盘把可视化查询当作一个普通组件即可,就和 Input 组件一样调用即可,否不需要关心对方用的数据流方案是什么。

实际上,应用间都应该改造为这种内部数据流方案,如果设计应用间调用,当作普通组件一样引用就行了。

OpenAPI 与数据流解耦

经过大家的努力,可视化查询与 OpenAPI 的关系、调用方式逐渐变得正规了起来:OpenAPI 提供 useExternal 函数注册开放能力,应用只需要在合适的时机调用即可,且注册的函数以接口形式暴露,实现方式由应用决定,所以即便应用从 useVizQuery 直接取出函数对接,也不显得多此一举,因为这对解耦设计是值得的。

比如调用 dataSet 获取数据集,再也不用和 dva 数据流里的 IVizDataset 绑定了,而是由应用决定如何实现。

遇到的挑战

这种类似方案几年前就已经被广泛讨论了,dva 其实已经过时好几年了,可能除了大家平时业务都比较忙以外,还一个重要原因是 dva 嵌入 DataWind 业务太深,依赖就像一张蜘蛛网,盘根错节,牵一发而动全身。

从一开始修改时就要做好把所有项目文件都改一遍的觉悟,这既是一件技术活,也是一件体力活,你得同时愿意付出脑力与体力,连续付出好几天重复劳动的时间,才能将你的思考落地。

体力劳动最麻烦的地方是修改引用,使用最广泛的是 schemavizDatavizQuery 这三个变量,即便搜索范围限定在可视化查询内(最后在全局也要搜索一遍,可能其它模块会调用),还是涉及几十个文件:

另一个问题是数据流之间的引用,可视化查询使用 dva 创建了 5 个 model 文件,它们相互依赖:

比如先迁移 vizQuery,会发现引用了不少 dataSet 提供的函数,想想也知道 hooks API 不可能容易调用到 dva 数据流,所以只能先迁移 dataSet,但 dataSet 提供了不少变量在 vizQuery 被引用,同时 vizQuery 还通过 dva 语法糖调用 dataSet 提供的函数!这其实是个死结,想要迁移必须一口气全部同时迁移,所以解法是做好一口气迁移的准备,迁移时从零依赖的开始,被引用了就用 window 变量做个垫片,然后快速逐步迁移,直到全部完成替换。

附录

Hookstore 介绍图

本文由哈喽比特于1年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/CuygDCO5zeFHZQAVvmakgw

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:7月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:7月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:7月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:7月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:7月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:7月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:7月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:7月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:7月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:7月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:7月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:7月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:7月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:7月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:7月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:7月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:7月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:7月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:7月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:7月以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  236873次阅读
vscode超好用的代码书签插件Bookmarks 1年以前  |  6881次阅读
 目录