AngularJS 项目重构

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

git-diff.jpg

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 实现,对后期修改以及代码测试都带来了很大的方便

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

/** 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 文件

/** 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 是一个好习惯。比如

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

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

使用 controllerAs

直接在路由使用 controllerAs

.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 代码互不干扰。

所以,其实我做的只是便于后期书写而已,总不能继续在这个文件书写下去,大坑啊。。