袋鼠云研发手记 | 袋鼠云EasyManager的TypeScript重构纪要

做为一家创新驱动的科技公司,袋鼠云每一年研发投入达数千万,公司80%员工都是技术人员,袋鼠云产品家族包括企业级一站式数据中台PaaS数栈交互式数据可视化大屏开发平台Easy[V]等产品也在迅速迭代。在进行产品研发的过程当中,技术小哥哥们能文能武,不断提高产品性能和体验的同时,也把这些提高和优化过程记录下来,现录入“袋鼠云研发手记”专栏中,以和业内童鞋们分享交流。css

下为“袋鼠云研发手记”专栏第一期,本期做者为袋鼠云前端团队。前端

 

袋鼠云前端团队-知乎专栏@DTUX

袋鼠云UX团队拥有十多名专家级别,经验丰富的前端开发工程师,分别支撑公司大数栈产品线的不一样子项目的开发需求,具体包括数据中台产品「数栈」与数据可视化产品Easy[V]两大块。react

在长期的项目实践与产品迭代过程当中,团队成员在 React 技术栈、数据可视化技术、前端工程化等细分领域上不断深耕探索,积累了丰富的经验与最佳实践,并分享在知乎专栏@DTUX。webpack

 

 

第一期

袋鼠云 EasyManager 的 TypeScript 重构纪要

前言

在 2018 年 Stack overflow 的开发者调查结果中,开发者们最爱的语言一栏中TypeScript 超越了 JavaScript 位居第四。web

相较于 JavaScript,开发者们更喜欢 TypeScript 将类型系统带入了 JavaScript 中,如此一来,开发者可以在运行程序以前发现更多的潜在的问题;得益于 TypeScript 编译器的良好支持,结合VSCode, 它还能够提示咱们该如何修复这些问题;将类型系统添加进 JavaScript, 同时容许编辑器给开发者提供更多的便利,好比代码补全、更方便的进行项目重构以及自动的模块导入等等。json

2018 Stack overflow调查结果redux

 

在 TypeScript 官网中列出了不少已经使用 TypeScript 的机构,一样的还有著名测试框架 Jest 也表示尝试将 Jest 迁移到 TypeScript。后端

TypeScript官网展现的已使用TypeScript机构前端工程化

 

TypeScript 的发展势头可见一斑。api

 

Easy Manager

Easy Manager是一款产品安装部署工具,是数栈大数据平台的运维管家,使用Easy Manager可实现数栈产品的安装部署。

在产品安装部署完成后,Easy Manager支持完成大数据平台全方位的运维,包含产品升级、扩缩节点、版本回滚、产品卸载、集群监控、实时告警等功能,致力于帮助客户最大化地节省运维成本,下降线上故障率与运维难度,提供安全稳定的产品部署与监控。

Easy Manager 目前已支持百至千个节点的集群部署,可进行大集群的部署、监控及平常运维。同时,除支持数栈大数据平台部署外,也可支持第三方产品的安装部署。

 

关于这次重构

本次 EasyManager 的重构主要目标是使用 TypeScript 替代 JavaScript,加上类型约束提升团队协做效率,同时剔除已经不须要的冗余代码。

在本次 EasyManager (下文简写成:EM)的重构以前,已经进行了一部分的重构工做,以前是基于EM2.3 版本进行重构的,可是发现 EM2.4 版本改动较大,须要在以前重构基础上继续进行重构工做。计划是一边开发  EM2.5版本,一边进行 EM2.4 版本的重构工做,在EM2.5基础上,最后进行  EM2.6 版本的开发同时进行遗漏补缺。

对于已经部署使用了EM2.5 版本的客户和将来即将部署使用EM2.6 版本的客户状况,咱们保留了 js_master 和 ts_master 两个分支。前者主要进行EM2.5 版本的 bug 修复为客户作支持,后者则是主版本进行版本迭代开发。

整个过程仍是比较顺利的,没有遇到阻塞工做流程的状况,下面详细来为你们分析一下本次重构详细状况。

js VS ts 结构区别与利弊

js 版本的目录结构并不属于那种一眼就能看懂的,其相似数栈将每一个页面所需的 action、reducer、assets 以及模块须要的其余资源都放在了一个以模块名称命名的文件夹里,个人理解是这里是以“模块”为思想进行的开发结构搭建,比较方便开发,对人协同开发对 changelog 亦能一目了然,管理 redux 也很容易定位。

以下图:

旧版本基本目录结构

ts 版本在这里作了更改,再也不是以“模块”为单位进行目录区分,而是以文件“功能”为单位进行搭建,具体表现是:js 版本的 service 模块文件夹下有 actions、reducers、pages、assets 文件夹以及 action types 文件 constants.js 和页面所需的 json 数据文件。而在 ts 版本,actions 如今以单个文件的形式存在于 src 目录下的 actions 文件夹下面,同理,reducers 则已单个文件形式存在于 stores 文件夹下,action types 则与路由文件 routers 和 api 接口定义 api.js 文件同在 constants 文件夹下;这样一来,ts 版本里 pages 目录下各个页面的文件夹下只有一个 index.tsx 入口文件、style.scss 样式文件和**.tsx 的页面主文件,能够说是很精简的目录组成。

以下图:

新版本的目录结构

两个结构各有利弊,ts 版本的结构比较经典,开发者花很短的时间就能读懂搭建者的心思与意图,对新手参与开发很友好,可是对于“按功能区分”文件结构的方式来说,开发一个模块须要游离在 actions、pages 和 stores 文件夹里,在模块多了以后文件就会变得难以定位和区分。js 版本的目录结构相较于 ts 版本的更深一些,主要是将页面全部资源都存放在页面本身目录下,这种结构很大程度上方便了多人协做开发,也方便定位页面所需的功能文件,惟一不足也许就是对新接触的开发者来讲要花点时间熟悉。

 

重构思路

ts 能够做为类型检查和编译工具使用,对于EasyManager来说主要做用在于类型检查,所以本次重构使用 webpack、babel 等插件进行项目编译。本次重构以模块为单位进行重构,重构顺序依次是 host、product、dashboard、service。

本次重构原则是尽可能不改动业务逻辑代码,对已有的重构基础进行包容叠加新特性,尽可能避免改动很是基础的东西,好比目录结构等。重构工做是开着 JavaScript 版本的EasyManager 和 TypeScript 版本的EasyManager, 比较模块功能异同进行同步,同时方便梳理EasyManager功能(得益于目前EasyManager 功能不是很复杂), 在 2.6 版本测试期间发现不少功能与 2.5 版本的不一样,甚至仍是 2.3 版本,所以建议在项目重构工做开始前根据PRD 和产品进行一次完整的功能梳理避免遗漏。

 

重构内容

重构内容主要分为代码层面重构和结构层面重构:

代码层面重构主要工做是提取页面 props 和 state 建立 interface 再经过 React.Component 传给组件,而后须要的把一些参数加上类型就能够。须要注意的是,页面/组件须要的 prop 必须如今 interface 中声明出来,一个组件可能包含 redux 的 state 和其父组件传入的 props,此时要将 redux 的 state 设置成可选,这样父组件才能够在不指定其值的状况下使用。

代码层面重构改变的东西并很少,样式文件直接拷贝内容就能够。

结构重构主要是将文章开始讲的那种以模块为单位进行划分的目录结构改为以 ts 文件类型/功能进行划分。

TypeScript 版本的 EM 将各个 action 和各个 reducer 集中在了一个文件夹下,所以每一个模块都须要将其相应的 action 和 reducer 新建到 actions 和 stores 文件夹内。

页面内容集中在 pages 文件夹下,其中的一个模块的具体目录长这样:

重构以后的Dashboard模块

 

style.scss 便是这个模块的样式文件,index.tsx 文件是此模块的入口,dashboard.tsx 则是此模块的逻辑文件,detail 则是此模块的子页面,也包括一个页面文件和入口文件,其余文件则是页面所需的数据、组件等文件。以前的模块构成是这样的:

重构以前的Dashboard模块

跟上面的比起来多了 redux 使用的 actions 和 reducer 文件,TypeScript 版本的已经将 action 和 reducer 提取到 src 目录下的 actions 文件夹和 stores 文件夹。

重构步骤

重构内容在项目跑起来以后就主要包括四个步骤:

Step 1 迁移业务逻辑代码

迁移业务逻辑代码是整个过程当中最为繁琐的。

迁移步骤大概可分为:页面主文件代码迁移、service 层迁移,redux 层迁移。

 

01 页面主文件代码迁移

将页面代码 copy 到新文件里便可,得益于 vscode 对 TypeScript 的良好支持,页面中的错误都会有提示,以下图:

VsCode智能提示

咱们对这种错误进行处理就能够了,常见的问题有总结,详情见重构注意事项。

 

02 service 层迁移

service 层迁移分为两步:补充 api、补充 service

这层迁移仍是很方便的,copy 页面代码以后,若是有页面直接调用 service 请求可是当前却没有这个方法的话编译器会报错,咱们在页面文件里根据这个错误逐一添加便可。

 

03 redux 层迁移

redux 层迁移分为 6 步:

 

1. 定义 Reducer 的 model(数据模型)
modal 文件定义了全部 reducer 使用的数据结构模型,在具体的 reducer 中咱们使用此模型约束 redux 数据结构: 暴露Store接口。

暴露Store接口

 

2. 迁移 reducer 文件
迁移 reducer 直接把原来的代码 copy 便可,须要注意的是在这里初始化 redux 数据的时候,须要指定此 state 的数据模型:

reducer里使用约束


同时还须要暴露此数据模型供页面使用,避免页面开发出现 undefined 之类的错误,同时得益于 vscode 对 TypeScript 的良好支持可以必定程度的提升咱们的开发效率(在当前版本的 em 里面没有用到这个导出,目前用到的是下面的 index 文件统一包装导出的 modal 接口)。

3. 补充 reducer 的 index 文件
reducer 通过 index 的统一导出给全部页面提供了全部 modal 的数据结构接口:

暴露全部store接口供使用


在页面中使用 AppStoreTypes 便可:

在pages里使用暴露的store接口进行约束


4. 补充 actionType 定义

补充 actionType 即把 2.5 版本的 action 使用的标识补充到新版本的 actionTypes 定义文件便可。

5. 迁移 action 文件
迁移 action 在把以前版本的 action 迁移过来以后另外须要导出此 action 的函数列表接口,这跟 reducer 导出 modal 是同样的意义,开发页面时能够直接进行使用:

暴露action函数提升开发效率

 

6. 使用:

使用暴露的action函数接口

 

这样咱们在使用 actions 的时候浏览器就可以智能提示了,同时对函数的参数也会严格限制。

 

Step 2 定义路由

本次重构过程因将 React Router V3 版本升级到了 V4 版本,所以路由部分改动蛮大。关于 React Router V3 迁移 V4 的详细内容你们能够在网上找到不少相关文章,本文在“重构障碍”中会详细聊聊。

定义路由主要工做就是将页面地址绑定到模块,由于使用了路由由父组件进行定义,因此总的路由文件很精简:

根路由文件

 

Step 3 迁移 CSS 内容

迁移 CSS 内容是整个过程当中最使人温馨的部分啦!直接将已有的 CSS 文件 copy 到新目录下而后引用便可。

须要注意的是本次EasyManager重构加入了对 antd 组件微调的效果,咱们除了全局样式 base.scss 以外还有一个用于 antd 组件的样式文件 dtemStyle.scss 文件:

用于全局规定antd组件微调的样式文件

 

在开发过程当中咱们会注意组件样式是否全局统一,设计是否有意差别化等问题,尽可能统一对 antd 组件的微调标准。

 

Step 4 测试

每一个模块基本都是遵循此步骤进行重构。期间,复杂的地方主要在第一步和第二步,迁移业务逻辑代码将须要迁移页面 jsx 代码、页面 redux 的 action 以及 reducer 代码。第二步的复杂体如今涉及到嵌套路由的应用上,这块咱们在“重构障碍”里详细解释。

 

重构障碍

React Router V4 的嵌套路由

React Router V4 版本相对于 二、3 版本改动较大,思想上也更向React 思想靠近。如今 React Router 取消了在一处管理全部页面路由的方式,取而代之的是在各个容器里进行管理。Router 像一个字组件同样放在了父容器里。

下面放一个官方 Demo 截图:

react-router4官方示例

本次重构过程当中遇到了嵌入页面没法正常显示问题,在发现不能够像 route v3 那样在一个文件里统一进行配置路由以后,我修改了每一个模块的入口文件 index.js,把其改为了 react-router 4 风格的路由定义文件,以下图:其中 props.match.path 指向父组件导航过来的当前路由,默认渲染第一个组件,好比从 A 页面点击/tob,那么/tob 就是渲染的 AlertChannel 组件,/tob/addAlertChannel 则渲染 AddAertChannelPanel 组件。(路由从配置文件变成了组件)

 

browserRouter 形成子组件页面刷新白屏问题,在 HTML 里加入根目录便可:

在HTML中解决,也可经过webpack解决

 

react refs 问题


主要情景是用户搜索特定 menu 以后咱们须要将选中的那个 menu 滚动到可视区域,使用 react 的 refs 的话会这样:

vs提示不存在属性

提示 Element 上没有咱们须要的属性,固然了咱们能够直接使用(this.refs.container as any).current 来规避 TypeScript 的错误提醒:

万物皆any解决

 

这样虽好,可是却违反了咱们使用 TypeScript 的初衷:类型约束。若是开发者使用这个方法随便写了一个不存在的特性,那么又出现了常见的 undefined 错误。所以这里建议使用私有变量设定值为 react 建立制定 DOM 类型的 Ref:

建立响应类型的REF

 

在 dom 上绑定这个 ref:

绑定至dom

而后直接使用便可:

正常使用

重构常见处理

导入 react 由于 react 没有默认导出报错:

改为:

antd 的 Form.create 传入了组件 form 属性,可是 ts 不知道:

 

此时导入 antd 的 formProps 合并到组件便可:

 

 

使用 router 传入的 location 等等 props 须要在组件 prop 数据模型接口里声明,不然 TypeScript 也会报错的:

 

重构总结与下一步

 

关于 MVC

相较于 Vue 的 MVVM 思想,放佛 MVC 更能让人找到实践的入口点。结合 TypeScript,咱们进一步的向 MVC 思想靠拢:

简化版MVC流程图

 

咱们将类型约束加入到了 pages 使用 stores 的过程当中,固然还包括 page 中本身的 state 或者父子组件的 prop 等也加入了类型约束。

 

按照 MVC 的思想,咱们全部的操做都要由 actions 去完成,笔者的想法是不须要放到 redux 中的就不须要放,state 已经足够好用,若是仅仅是为了遵循 MVC 思想而把没有必要放在全局数据的变量放到全局里那对后来者理解项目代码和模块化开发来说将是难以接受的。

 

所以笔者的建议是能不放 redux 的尽可能不放,提升项目的模块化程度。

 

重构不足

本次重构过于仓促,重构前的准备工做也没有作的很充分。本次最大的不足有如下几点:

  1. 使用了过多的 any。包括但不局限于:服务器返回的数据格式,service 层请求入参格式以及各个函数的出参格式。

  2. model 体现过于薄弱。在 reducer 中或者是页面 state 中都缺乏了集中统一的 model 定义。

 

下一步

EM2.6 版本仅仅是完成了 JavaScript 到 TypeScript 的迁移,咱们接下来的工做就是要充分发挥 TypeScript 的优点,下一步咱们会将 service 层的入参进行接口定义,而后对后端返回的数据进行接口定义。而后剥离如今在一块儿的 reducer 的 store 接口们,咱们会将它们分开存放便于查找与修改。

相关文章
相关标签/搜索