今天主要和大家分享四个内容:第一,是为什么我们要选择 Angular;第二,是我们在使用 Angular 过程中总结的一些比较好的实践;第三,是怎样更好的组织项目结构;第四,是谈一下我对整合 Angular 的 ES6 和 JSPM 的见解。
选择 Angular 的原因
为什么选择 Angular 呢?由于团队原因,当时我们只有三名程序员,两个后端一个前端。
效果有两个:第一个是做到前后端分离,因为传统的模式效率太低了;第二个是应用逻辑与 DOM 解耦,做前端开发的同学也都应该知道 DOM 的操作实际上是很烦的,我们不希望把时间浪费这上面。总的来说就是希望有一个东西可以大大提高我们的生产力。
下图是在 Angular 社区传得比较火的一张图,也在一定程度上体现了我们在使用这个框架过程中的感受。
为什么要继续使用 Angular 呢?首先我觉得世界上没有任何完美的框架,每一个框架都有自己的优点和缺点,而实践证明了 Angular 可以大幅提高我们的生产力,另外我们可以通过采用更好的实践来避免 Angular 的一些缺点。此外,谷歌和 Angular 正在变得越来越好。
上面这个是 Angular 版本 1.2 和 1.3 的对比图,可以看到从 1.2 升到 1.3 版本之后,DOM 操作快了 4.4 倍,内存使用少了 73% 。并且上个月 Angular 出了 1.4 版本,性能也是有很大提升的。
Angular 使用实践
下面分享一下我们在使用 Angular 当中收获的一些比较好的实践。
第一,我建议大家在用 Controller 时选择 Controller As。这是在 Angular 1.2 版本加入的一个新特性,当我们使用 Controller As 的时候可以把需要的东西直接赋值给 this 。它还有一个好处,是可以避免 Scopo 继承带来的影响。因为 Scopo 是具有原型继承的,当 Scopo 在视图里面嵌套时,我们是很难追踪到数据来源的。这也很多刚刚接触 Angular 的同学比较难以理解的问题,因此使用 Angular AS 可以帮我们避免这个问题。
第二,是精简 Controller 。使用 Controller 一个很重要的原则,是不要将业务代码写到里面。因为 Controller 跟 View 就像两兄弟,他们是绑在一块的。这样就产生了一个很不好的地方是 controller 无法复用。而我们的一些业务代码很多情况下都是需要共用的,这就造成了很多麻烦。
上图是一个 Angular 简单的示意图,Angular 是有 Services 这个概念的,而 Services 可以注入到其他地方去。它的一个主要职责是获取和装饰数据,并且把数据共享给 Controller ,Controller 则主要是暴露数据和方法给 View 层。一个比较好的实践,是我们应该把 Controller 跟 Services 分开来,把业务逻辑代码写在 Services 里面。
比如这个 TodoCtrl 。它定义了两个方法,一个是 addTodo ,一个是 removefromTodos。可以看到两个方法里面包含的一些业务逻辑代码,但显然这样做是不好的,我们应该避免加以避免。我们应该把这种业务以及代码放在 Services 层,这里我们定义了一个 Todo 的 Services 。暴露出去的有两个方法,还有一个数据,当我们把业务代码分离出来的时候,可以看到 Controller 是精简了很多的,它是不属于业务逻辑的,仅仅是接受 model ,而 model 是来自 Services 层的。
第三,我建议大家在路由层使用 Resolve 。一般一个路由定义是有自身的一个 Controller 还有模版的,当我们使用这个 Resolve 的时候,可以在激活 Controller 前去加载所需的数据。
比如说这里面有 4 个 tab ,每一个 tab 分别代表不同的路由,而它下面的内容除了数据不同之外,展现方式都是一样的,说明它们的 Controller 和模板是一样的。而这时候如果我们结合 Resolve 的话,就可以更好的共用这个 Controller 和模板。
这个是一个路由配置,上面是热门圈子,下面是足球,可以看到我们用了两个路由配置,用了相同的模板和 Controller ,仅仅不同的地方是数据,是 Resolve 的数据。这两个数据是从 Servicse 那边过来的,而且 Resolve 的数据是可以注入,当我们把这个数据注到 Controller 里面去时,可以看到 Controller 暴露出去的数据仅仅是从 Services 过来的,这样,就可以很有效的共用到我们的 Controller ,还有模板。
第四点实践,是基于性能考虑,建议大家使用单向数据绑定。在 1.3 以上的版本,Angular 是有自带单向数据绑定的,假如说你用的是 1.3 以下的一个版本,建议大家用 bindonce 这个第三方库。
如何更好地组织项目结构
下面要跟大家分享的,是如何更好地组织项目结构。
这是两种比较常用的项目结构,一种是按照文件类型划分,一种是按照功能模块划分。
第一种类型的优点是结构很清晰:把 Controller 的文件丢到 Controller 这个目录下, Services 文件就在 Services 目录下。这种划分方式是我在刚刚接触 Angular 的时候用的一个目录结构。但两个月之后发现,这个目录结构存在一个很大的缺点,特别是当你文件很多时,可能一个 Controller 文件夹里面有十几到二十几个个文件,这会导致你找文件很困难。
第二种方法,也就是按照功能模块划分,是我们的项目目前在用的。它的优点是每一个文件夹都是一个模块,让你可以很快速找到相应文件,因为跟这个模块相关的文件都在它的目录下面。此外,以功能模块来组织项目比较容易扩展。当我们需要一个新的模块,我们只需要再建一个文件夹就 OK 了。
无论使用哪一种结构都要保持一致性。在我们定义的项目结构中,可以看到每一个文件夹下的模块文件都有自己的命名方式, Controller 文件的命名方式。这样的话当你看到这个文件时就会知道它的用途,特别是当你团队人比较多时,会有很大帮助。
ES6 与 JSPM 之我见
下面简要分析一下我们在使用 Angular 的过程中遇到的几个问题。
首先,是由于我们没有使用文件依赖库,因此在 Index.html 会引用一堆 JS 文件。有人说为什么你们不用像 requirejs 这样的第三方模块加载呢?这点我觉得 Angular 它本身是有依赖注入的,并且它跟 requirejs 是有一定的冲突的,所以我们一直没有跟 requirejs 结合起来,并且现在 ES6 已经出了,相信 requirejs 这种东西是慢慢会被淘汰掉的。
我们是怎么样解决这个问题的呢?我们创建了一个 gulp inject 任务,这个任务可以帮助我们自动 Inject 相关的 JS 文件。但是有可能会出现这样一个情况,刚刚接触 Angular 的同学经常会遇到。
为什么会出这样一个错误呢?这个是我们 inject 过后的一个文件,我们希望把模块定义文件放在最上面,其他在模块上面的文件放在下面。
比较好的是我们模块命名有自己的组织方式,可以优先 injects 模块文件,我后面会讲到一个更好的解决方案。
此外是样式与模块分离。这样的组织文件方式有个缺点,是文件对应有困难,这是因为每一个文件会有自身的模板文件,可能还会有 SCSS 文件。这样当每一个文件都在不同的目录下时,实际上你是很难维护和修改的,并且会对我们做组件化造成很大困难。
所以我们在用了这个文件目录之后,也认识到这不是很好的方式。Web Component 是未来发展的方向,包括现在 Angular 2.0 也是在朝这个方向发展。一个好的组件是可以很有效的降低开发和设计成本的。当然我们现在在用 Angular 1.X 的时候,也是可以通过 Directive 的方式来组织我们的项目。
比如这个网页,我们把每一块都分成一个小小的框,每一个框里面是分别对应不同的一个组件,这个页面实际上是一个大的组件,Directive 是 Home 。它这个模板文件里面也包含了各种小的组件,比如说导航条、发布模块。每一个方框都是一个小小的组件,这个可以很好组织我们的 APP 。
下面谈一下 ES6 ,ES6 在上个月正式发布了,它添加了上一代语言的一些特性,比如说模块加载。当然现在我们是有合适的环境去使用它的,这个是目前 ES6 的环境,比如说现在已经有一些转化工具是可以帮助我们把 ES6 语言转化成 ES5 的,当然我推荐大家使用 Babel 这个工具。
ES6 自带一个模块加载,还有基于 ES6 模块加载的一个 SystemJS 。模块管理有 JSPM , 它是基于 SystemJS 的一个 Javascript 包管理器,我们通过这个包管理器加载的包是遵循 SystemJS 这个规范的。我们也可以通过端点如 npm 或者加载各种格式的模块,包括 ES6, AMD ,还有 CommomJS ,它还有一个完善的打包功能,可以把我们的应用,包括 ES6 的转换和压缩进行一次打包。
当我们使用 Angular 结合上面提到的一些工具的时候,可以很好的去构建下一代应用,这个是我使用 ES6 和 JSPM 的一个小小的案例。可以看一下左边的目录结构,APP 是程序的一个总目录。它里面有两个文件夹,Common 和 components 。Common 文件里面包含一些共用组件;Components 文件夹是一个独立的组件;当然 APP 其实也是一个组件。这个组件依赖于 Controller 文件夹里面的所有组件,以及 Components 里面的所有组件。左边还有一些 JSPM 的包文件和配置文件。
使用 JSPM 有一个很好的地方,是我们可以不用像之前那样加载一大串文件,因为它已经帮我们做了很好的依赖管理。APP 的文件是整个程序的入口,它里面定义了主模块 APP 还有依赖的第三方模块。
而使用 ES6 和 JSPM 的好处,首先是模块系统,当我们使用模块系统时,可以把每一个文件的颗粒度做到很小。其次是 Classes ,它可以让我们不必写以前那样的原型继承。当然,在使用 Classes 时需要注意一个问题:Controller 里面会注入一些其他的服务,这时应该把我们的服务放在构造函数里面去,以便原型方法可以访问到。
另外一个比较好的地方是 Arrow Function,它同时具有一个保留执行上下文的特性。
我们可以看到上面的 JSPM 除了加载 JS 文件之外,还可以加载一些其他文件,比如说普通文本。这样的好处是我们可以不必去定义 templateUrl ,减少掉 $templateCache这一步的操作。
因此我建议大家使用 ES6,因为现在它已经成为了一个标准。虽然目前还有很多浏览器不是很兼容,但是我们可以借助第三方的工具将它转成 ES5 的语言。
游志军 洋葱圈前端团队负责人
热爱前端工程与自动化,崇尚前后端分离,近年来一直致力于 Angular 的开发