14

不知不觉已是2019年的7月,恍惚之间已工作四年。懵懵懂懂的成长,间歇性努力,实话说,对现在自己取得的成果不大满意。不过,好在时不时顿悟,知道适时作出改变。

此后发文会适当记录一些心路历程,与君共勉。

欢迎Star和订阅我的博客

本文要点:

  1. 什么项目,为何会重构?
  2. 怎么重构的?
  3. 重构前后对比

什么项目,为何会重构?

项目是公司主打业务产品之一的可视化子项目,与其他子项目几乎没有耦合,所以可以单独拎出来重构。具体业务不作过多描述,主要重构内容为系统组件和一个自研可视化图形库。重构总共耗时30个工作日。

技术主要用Vue2系列和JavaScript。第一个重构原因就是没有引入静态类型,导致查看一个对象结构需要翻来覆去在多个文件中查找。第二是因为之前新增代码模式一般为:“来一个需求加一段代码”,长期积累导致代码结构混乱,可读性差。第三是各个状态模块耦合度高,加大了代码维护难度。

怎么重构的?

一、在JavaScript中使用TypeScript。“什么?在JS中使用TS? 闻所未闻。 ” 在看到TS官网手册最后一条"Type Checking JavaScript File"之前,我也这样想。其实,TS和VSCode(一款IDE)结合,也可以实现静态类型检测,只不过使用注释形式,一样支持tsconfig.json和自定义Typing。

type TypeApple = { name: string, count: number }
/** @type {TypeApple} */
const apple = { name: 'foo', count: 100 }

二、细化模块分类。一般情况下,模块都会有耦合。但如果耦合度过高,往往是因为模块没有细分到位。如果细化模块?举例,假如有一个模块叫Operation,里面既包含操作相关逻辑,也有操作面板逻辑。随着业务发展,操作面板逻辑越来越多。我们完全可以将操作面板逻辑单独抽成一个模块OperationPanel

三、解耦可视化库和Vue/Vuex。写业务的时候,很容易因为方便,在Vue组件或Vuex模块中代码越写越长,越来越难维护。这个项目也不列外。所以重构的时候,单独将可视化库相关逻辑抽成模块,并使用类Vuex写法(state, getters, mutations, actions)进行管理。

class Counter {
  // # state  
  /** @type {number} */
  count = 0

  // # getters
  get countText() { return `Count is: ${ this.count }` }

  // # mutations
  /** @param {number} count*/
  SET_COUNT = count => { this.count = count }
  
  // # actions
  /** @param {number} count*/
  logCount = ( count ) => {
    this.SET_COUNT( count )
    console.log( this.countText )
  }
}

四、最后一条,编写可维护性高的代码。这里说两个方法。

第一个是“使用Map。处理“一个有多类型的数据”需要使用判断,常见有3种方法:If, Switch, MapIf的使用简单粗暴,容易理解。

if ( animalType === 'dog' ) {
    console.log( 'Wang!' )
} else if ( animalType === 'cat' ) {
    console.log( 'Miao!' )
} else if ( animalType === 'bird' ) {
    console.log( 'Jiu!' )
}

Switch可以看做是If的简化。

switch ( animalType ) {
    case 'dog':
      console.log( 'Wang!' )
      break
    case 'cat':
      console.log( 'Miao!' )
      break
    case 'bird':
      console.log( 'Jiu!' )
      break
}

Map针对性最强,并且最简洁、最易于维护。

const logMap = {
    dog: () => console.log( 'Wang!' ),
    cat: () => console.log( 'Miao!' ),
    bird: () => console.log( 'Jiu!' ),
}
logMap[ animalType ]()

具体使用也哪一种因场景而异,但多数场景下,使用Map可读性更强。

第二个是“使用getters和mutations”。比如定义一个模块的`:operationGetters.js`, 里面提供各种用来获取与操作有关的常量和方法。

export const OPERATION_TYPE_A = 0
export const OPERATION_TYPE_B = 1

export const OPERATION_TITLE_MAP = {
  [ OPERATION_TYPE_A ]: 'Title A',
  [ OPERATION_TYPE_B ]: 'Title B',
}

export const getOperationTitleByType = type => OPERATION_TITLE_MAP[ type ]

定义mutations则是定义一个提供相关各种变更数据方法的文件。在维护代码的时候,查找变更方法名即可直接找到更改数据的出处。

export const SET_OPERATION_TITLE = ( operation, title ) => { operation.title = title }

重构前后对比

代码类型 重构前 重构后
可视化工具库 245kb 214kb
组件 171kb 157kb
状态管理 62kb 60kb
模块模型 15kb 19kb
服务请求 12kb 4kb
常量 18kb 22kb
工具(Utils) 15kb 19kb
静态类型(TypeScript) 0kb 9kb
合计 521kb 509 kb

因为边重构边在加新功能,且添加了定义TypeScript静态类型的代码,所以总代码量减少并不多。

不过性能显著提升,最重要的是代码可读性、可维护性大大增强,从而能够更从容地应对新需求。

感谢你花时间阅读这篇文章。如果你喜欢这篇文章,欢迎点赞、收藏和分享,让更多的人看到这篇文章,这也是对我最大的鼓励和支持!
欢迎Star和订阅我的原创前端技术博客


苏溪云
759 声望62 粉丝

5年前端, 分享最简单易懂技术文章