上学期由于期末停工的项目又要继续开展了,然而停了一个多月的时间,我已经看不下去他的代码了,简直惨不忍睹,花了我将近40个小时的时间去做了重构。虽然重构说明有进步了,但是一改就要改几十个页面啊…累觉不爱..说一说这次将近40小时的重构吧。
git-diff

Angular 重构

项目是基于 Angular 的 SPA,项目参考Angular规范进行重构,主要是以下几点:

  • 把控制器的业务逻辑(主要是 HTTP 请求)分离到 Factory
  • Controller 和 Directive 以及 Factory 全部用立即函数包装
  • Controller 和 Directive 以及 Factory 内部书写格式
  • 使用 controllerAs 代替 $scope
  • 全部 JavaScript 文件使用 use strict 严格模式
  • 利用单体做部分数据的缓存
  • 提取大部分可复用模块到 directive
  • 全部 ng-repeat 加上 track by
  • 过大的试图使用 ng-include 进行分离
  • 去掉全部辅助变量,用 angular-promise-buttons 来达到按钮状态变化
  • 去掉全部页面切换动画
  • 手动进行依赖注入
  • 使用 ES6 语法,用 babel 转为 ES5
  • 使用 eslint 来做代码格式检查

之前我几乎没有使用 Factory 这一层,全部业务逻辑都在 Controller 里面做,随着项目越来越大(有26个页面),页面之间函数重复的情况很多,而且控制器太厚,可读性差,给维护带来了巨大的困难。在这次重构之中,我把全部的 HTTP 请求全部放在 Factory 实现,从而做到了以下几点:

  • 函数复用,多个控制器用一个 Factory,避免同个函数多次书写
  • HTTP 请求返回 promise,结合 angular-promise-buttons 做到了按钮状态的自动变化以及过渡效果,去掉了先前实现同样目的的全部辅助变量
  • 对部分相对不变的数据,在第一次缓存后直接在该 Factory 进行缓存,第二次获取的时候直接返回内存中的数据,加快了部分页面的二次加载速度,对跨页面你的同个请求同样有效
  • 容易做单元测试和更改逻辑,因为全部 HTTP 请求都放在 Factory 实现,对后期修改以及代码测试都带来了很大的方便

举个例子,这是我项目中的部分代码,现在是能拿出手了,以前的代码我都不能再吐槽了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/** IndexCtrl.js */
(function() { // 立即执行函数,避免作用域污染
'use strict'; // 严格模式,使代码更规范
// 空一行
angular // Angular控制器定义,控制器函数使用命名行数
.module('index')
.controller('IndexCtrl', IndexCtrl);
IndexCtrl.$inject = ['bookservice', 'booklistservice', 'slideservice']; // 手动依赖注入
// 再空一行
function IndexCtrl(bookservice, booklistservice, slideservice) { // 控制器函数
var vm = this; // 不使用$scope
vm.myInterval = 5000;
vm.getHotBooklists = getHotBooklists; // 置顶绑定成员,函数声明隐藏实现细节
getPopularBooks(); // 即时只使用一次,也推荐封装成函数
getSlides();
function getPopularBooks() {
return bookservice.getPopularBooks().then(response => {
vm.books = response;
});
}
function getHotBooklists() {
return booklistservice.getHotBooklists().then(response => {
vm.booklists = response;
});
}
function getSlides() {
return slideservice.getSlides().then(response => {
vm.slides = response;
});
}
}
})();

然后是 Factory 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/** SlideService.js */
(function() {
'use strict';
angular
.module('index')
.factory('slideservice', slideservice);
slideservice.$inject = ['$http', '$q'];
function slideservice($http, $q) {
let slides = null;
return {
getSlides: getSlides
};
function getSlides() {
if(slides === null) {
return $http.get(host + '/slides')
.then(response => {
slides = response.data; // 第一次获取后存入内存
return slides;
});
} else {
let deferred = $q.defer();
deferred.resolve(slides);
return deferred.promise; // 将数据封装入promise返回,保证透明性
}
}
}
})();

directive 就不给示例了,上面主要参考了这个Angular规范。这样写真的比我之前写的好了几百倍,controller 和 factory 和 directive 都按这个规范来,代码会很好维护。

这次重构让我知道我以前写的根本不叫 Angular,写的是一坨翔,MLGB 害我改了四天。

HTML 重构

其实说白了也是 Angular 重构,在上面的 Angular 重构已经提到了一些了,但是上面主要是说 controller 和 factory,这里说下 directive 和视图。

部分页面复用

在 Angular 中,HTML 部分复用有两种方案,一种是使用 ng-include,还有一种是使用 directive,其实区别很简单,ng-include 只是很简单的 HTML 复用,而 directive 你可以传递参数,directive 可以有自己的控制器,可以操纵 DOM,其实就是 HTML 和 JavaScript 文件的区别。不过这只是 directive 在页面复用这一块的作用,其实 directive 强大的很。

使用 ng-repeat 都带上 track by

对于 ng-repeat 使用 track by 可以提升性能,对于任何 ng-repeat 都加上 track by 是一个好习惯。比如

1
2
3
<ul>
<li ng-repeat="book in vm.books track by book.isbn">{{book.title}}</li>
</ul>

也可以直接使用 track by $index。

使用 controllerAs

直接在路由使用 controllerAs

1
2
3
4
5
6
7
8
9
10
.state('index', {
url: '/',
views: {
'main': {
templateUrl: 'index/index_tpl.html',
controller: 'IndexCtrl',
controllerAs: 'vm'
}
}
})

然后在视图中就像上面的例子,在访问控制器的变量和方法的时候都要带上 vm,虽然这样会稍微麻烦了一点,但是可以避免很多坑,而且这种写法更接近 JavaScript 原生的写法。

CSS 重构

恩,CSS 重构才是真正的大坑,先说下我之前是怎么写 CSS 代码的

  • 使用了 Sass 预处理器
  • 多层嵌套
  • 命名混乱
  • 过多复用
  • 全部挤在一个文件

恩,挤在一个文件里面是最要命的,而且很任性的进行嵌套,导致多了或者少了一层都可能出问题,而且是2000多行的代码…所以我其实并没有做什么重构,我把它从一个文件分成了很多个文件,每个视图一个专属 scss 文件,对于复用的部分页面,用 directive 替代,并给该 directive 专属的 scss 文件。
怎么做到专属呢,就是每个视图和每个 scss 都包装在一个类名里面,这样就保证了 scss 代码互不干扰。
所以,其实我做的只是便于后期书写而已,总不能继续在这个文件书写下去,大坑啊。。