7 月 5 日,又拍云 Open Talk 第 10 期 造访在广州贝塔空间。这场以《架构师专场:前后端高级实践与运用》为主题的分享会,邀请到了 酷狗音乐大数据架构师王劲,洋葱圈前端负责人游志军,又拍云 首席架构师张聪 三位业内“大拿”,向大家分享压箱底的技术实战。洋葱圈前端负责人游志军在活动上作了题为《Angular在洋葱圈的实践与思考》,以下是分享实录:

大家好,我是洋葱圈的前端负责人游志军,今天和大家分享的主题,是 Angular 在洋葱圈的实践与思考。

关于洋葱圈

“洋葱圈”是我们的核心产品,一年前我们对它的定义还是一个“体育移动社区”,但现在我们把移动这两个字去掉了,这是因为两个月前我们发布了第一个正式Web版,大家可以通过以下地址:sponia.com 访问。

image.png

在洋葱圈里,用户可以加入各种不同类型的体育圈子,和与你有共同兴趣的人一起讨论、吐槽。并且我们还拿到了欧洲顶级联赛数据提供商的授权,用户也可以在里面查看到各大联赛的各种数据。

我今天主要和大家分享四个内容:第一,是为什么我们要选择 Angular;第二,是我们在使用 Angular 过程中总结的一些比较好的实践;第三,是怎样更好的组织项目结构;第四,是谈一下我对整合 Angular 的 ES6 和 JSPM 的见解。

选择 Angular 的原因

为什么选择 Angular 呢?在洋葱圈 APP 发布不久后,我们有一个需求,做一个这样的数据中间页,可比较惨的是,当时我们只有三名程序员,两个后端一个前端。

而我们希望达到的效果有两个:第一个是做到前后端分离,因为传统的模式效率太低了;第二个是应用逻辑与 DOM 解耦,做前端开发的同学也都应该知道 DOM 的操作实际上是很烦的,我们不希望把时间浪费这上面。总的来说就是希望有一个东西可以大大提高我们的生产力。

image.png
作为一家一年多前才成立的创业公司,我们必须保持尽可能快的迭代速度。这也是我们要选择 Angular 的因素之一。

Angular 是谷歌在 09 年推出的一个框架。它有以下几点特性:第一是双向数据绑定,当数据发生变化的时候,不用我们手动去操作去 DOM ,视图就会自动更新。这跟我们前面提及到的应用逻辑和 DOM 分离是很符合的。第二是强大的指令,这是我喜欢 Angular 的一点,它可以很好的做到组件化。此外,Angular 还有一些其他的特性,比如依赖注入,以及代码结构清晰等。

当然 Angular 也存在一些不足之处。比如被人垢病的性能问题,当一个页面数据绑定过多时,性能就会有一定的瓶颈,而且涉及的概念比较多,不太容易学习。另外一点,是目前很多人想用 Angular 又不敢用的原因:它的 2.0 版本是不兼容之前版本的。

下图是在 Angular 社区传得比较火的一张图,也在一定程度上体现了我们在使用这个框架过程中的感受。

image.png
为什么要继续使用 Angular 呢?首先我觉得世界上没有任何完美的框架,每一个框架都有自己的优点和缺点,而实践证明了 Angular 可以大幅提高我们的生产力,另外我们可以通过采用更好的实践来避免 Angular 的一些缺点。此外,谷歌和 Angular 正在变得越来越好。

image.png
上面这个是 Angular 版本 1.2 和 1.3 的对比图,可以看到从 1.2 升到 1.3 版本之后,DOM 操作快了 4.4 倍,内存使用少了 73% 。并且上个月 Angular 出了 1.4 版本,性能也是有很大提升的。

Angular 使用实践

下面分享一下我们在使用 Angular 当中收获的一些比较好的实践。

image.png
第一,我建议大家在用 Controller 时选择 Controller As。这是在 Angular 1.2 版本加入的一个新特性,当我们使用 Controller As 的时候可以把需要的东西直接赋值给 this 。它还有一个好处,是可以避免 Scopo 继承带来的影响。因为 Scopo 是具有原型继承的,当 Scopo 在视图里面嵌套时,我们是很难追踪到数据来源的。这也很多刚刚接触 Angular 的同学比较难以理解的问题,因此使用 Angular AS 可以帮我们避免这个问题。

第二,是精简 Controller 。使用 Controller 一个很重要的原则,是不要将业务代码写到里面。因为 Controller 跟 View 就像两兄弟,他们是绑在一块的。这样就产生了一个很不好的地方是 controller 无法复用。而我们的一些业务代码很多情况下都是需要共用的,这就造成了很多麻烦。

image.png

上图是一个 Angular 简单的示意图,Angular 是有 Services 这个概念的,而 Services 可以注入到其他地方去。它的一个主要职责是获取和装饰数据,并且把数据共享给 Controller ,Controller 则主要是暴露数据和方法给 View 层。一个比较好的实践,是我们应该把 Controller 跟 Services 分开来,把业务逻辑代码写在 Services 里面。

image.png
比如这个 TodoCtrl 。它定义了两个方法,一个是 addTodo ,一个是 removefromTodos。可以看到两个方法里面包含的一些业务逻辑代码,但显然这样做是不好的,我们应该避免加以避免。我们应该把这种业务以及代码放在 Services 层,这里我们定义了一个 Todo 的 Services 。暴露出去的有两个方法,还有一个数据,当我们把业务代码分离出来的时候,可以看到 Controller 是精简了很多的,它是不属于业务逻辑的,仅仅是接受 model ,而 model 是来自 Services 层的。

第三,我建议大家在路由层使用 Resolve 。一般一个路由定义是有自身的一个 Controller 还有模版的,当我们使用这个 Resolve 的时候,可以在激活 Controller 前去加载所需的数据。

image.png
比如说这里面有 4 个 tab ,每一个 tab 分别代表不同的路由,而它下面的内容除了数据不同之外,展现方式都是一样的,说明它们的 Controller 和模板是一样的。而这时候如果我们结合 Resolve 的话,就可以更好的共用这个 Controller 和模板。

image.png
这个是一个路由配置,上面是热门圈子,下面是足球,可以看到我们用了两个路由配置,用了相同的模板和 Controller ,仅仅不同的地方是数据,是 Resolve 的数据。这两个数据是从 Servicse 那边过来的,而且 Resolve 的数据是可以注入,当我们把这个数据注到 Controller 里面去时,可以看到 Controller 暴露出去的数据仅仅是从 Services 过来的,这样,就可以很有效的共用到我们的 Controller ,还有模板。

image.png
第四点实践,是基于性能考虑,建议大家使用单向数据绑定。在 1.3 以上的版本,Angular 是有自带单向数据绑定的,假如说你用的是 1.3 以下的一个版本,建议大家用 bindonce 这个第三方库。

如何更好地组织项目结构

下面要跟大家分享的,是如何更好地组织项目结构。

image.png

这是两种比较常用的项目结构,一种是按照文件类型划分,一种是按照功能模块划分。

第一种类型的优点是结构很清晰:把 Controller 的文件丢到 Controller 这个目录下, Services 文件就在 Services 目录下。这种划分方式是我在刚刚接触 Angular 的时候用的一个目录结构。但两个月之后发现,这个目录结构存在一个很大的缺点,特别是当你文件很多时,可能一个 Controller 文件夹里面有十几到二十几个个文件,这会导致你找文件很困难。

第二种方法,也就是按照功能模块划分,是我们的项目目前在用的。它的优点是每一个文件夹都是一个模块,让你可以很快速找到相应文件,因为跟这个模块相关的文件都在它的目录下面。此外,以功能模块来组织项目比较容易扩展。当我们需要一个新的模块,我们只需要再建一个文件夹就 OK 了。

image.png
无论使用哪一种结构都要保持一致性。在我们定义的项目结构中,可以看到每一个文件夹下的模块文件都有自己的命名方式, Controller 文件的命名方式。这样的话当你看到这个文件时就会知道它的用途,特别是当你团队人比较多时,会有很大帮助。

ES6 与 JSPM 之我见

下面简要分析一下我们在使用 Angular 的过程中遇到的几个问题。

首先,是由于我们没有使用文件依赖库,因此在 Index.html 会引用一堆 JS 文件。有人说为什么你们不用像 requirejs 这样的第三方模块加载呢?这点我觉得 Angular 它本身是有依赖注入的,并且它跟 requirejs 是有一定的冲突的,所以我们一直没有跟 requirejs 结合起来,并且现在 ES6 已经出了,相信 requirejs 这种东西是慢慢会被淘汰掉的。

image.png
我们是怎么样解决这个问题的呢?我们创建了一个 gulp inject 任务,这个任务可以帮助我们自动 Inject 相关的 JS 文件。但是有可能会出现这样一个情况,刚刚接触 Angular 的同学经常会遇到。

image.png

为什么会出这样一个错误呢?这个是我们 inject 过后的一个文件,我们希望把模块定义文件放在顶部,其他在模块上面的文件放在下面。

比较好的是我们模块命名有自己的组织方式,可以优先 injects 模块文件,我后面会讲到一个更好的解决方案。

image.png
此外是样式与模块分离。这样的组织文件方式有个缺点,是文件对应有困难,这是因为每一个文件会有自身的模板文件,可能还会有 SCSS 文件。这样当每一个文件都在不同的目录下时,实际上你是很难维护和修改的,并且会对我们做组件化造成很大困难。

所以我们在用了这个文件目录之后,也认识到这不是很好的方式。Web Component 是未来发展的方向,包括现在 Angular 2.0 也是在朝这个方向发展。一个好的组件是可以很有效的降低开发和设计成本的。当然我们现在在用 Angular 1.X 的时候,也是可以通过 Directive 的方式来组织我们的项目。

image.png

比如这个网页,我们把每一块都分成一个小小的框,每一个框里面是分别对应不同的一个组件,这个页面实际上是一个大的组件,Directive 是 Home 。它这个模板文件里面也包含了各种小的组件,比如说导航条、发布模块。每一个方框都是一个小小的组件,这个可以很好组织我们的 APP 。

下面谈一下 ES6 ,ES6 在上个月正式发布了,它添加了上一代语言的一些特性,比如说模块加载。当然现在我们是有合适的环境去使用它的,这个是目前 ES6 的环境,比如说现在已经有一些转化工具是可以帮助我们把 ES6 语言转化成 ES5 的,当然我推荐大家使用 Babel 这个工具。

ES6 自带一个模块加载,还有基于 ES6 模块加载的一个 SystemJS 。模块管理有 JSPM , 它是基于 SystemJS 的一个 Javascript 包管理器,我们通过这个包管理器加载的包是遵循 SystemJS 这个规范的。我们也可以通过端点如 npm 或者加载各种格式的模块,包括 ES6, AMD ,还有 CommomJS ,它还有一个完善的打包功能,可以把我们的应用,包括 ES6 的转换和压缩进行一次打包。

image.png
当我们使用 Angular 结合上面提到的一些工具的时候,可以很好的去构建下一代应用,这个是我使用 ES6 和 JSPM 的一个小小的案例。可以看一下左边的目录结构,APP 是程序的一个总目录。它里面有两个文件夹,Common 和 components 。Common 文件里面包含一些共用组件;Components 文件夹是一个独立的组件;当然 APP 其实也是一个组件。这个组件依赖于 Controller 文件夹里面的所有组件,以及 Components 里面的所有组件。左边还有一些 JSPM 的包文件和配置文件。

使用 JSPM 有一个很好的地方,是我们可以不用像之前那样加载一大串文件,因为它已经帮我们做了很好的依赖管理。APP 的文件是整个程序的入口,它里面定义了主模块 APP 还有依赖的第三方模块。

image.png

而使用 ES6 和 JSPM 的好处,首先是模块系统,当我们使用模块系统时,可以把每一个文件的颗粒度做到很小。其次是 Classes ,它可以让我们不必写以前那样的原型继承。当然,在使用 Classes 时需要注意一个问题:Controller 里面会注入一些其他的服务,这时应该把我们的服务放在构造函数里面去,以便原型方法可以访问到。

另外一个比较好的地方是 Arrow Function,它同时具有一个保留执行上下文的特性。

image.png

我们可以看到上面的 JSPM 除了加载 JS 文件之外,还可以加载一些其他文件,比如说普通文本。这样的好处是我们可以不必去定义 templateUrl ,减少掉 $templateCache这一步的操作。

因此我建议大家使用 ES6,因为现在它已经成为了一个标准。虽然目前还有很多浏览器不是很兼容,但是我们可以借助第三方的工具将它转成 ES5 的语言。

今天分享的就是这么多,谢谢大家!