第543期Angular 2 核心模块剖析
Posted 前端早读课
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第543期Angular 2 核心模块剖析相关的知识,希望对你有一定的参考价值。
前言
这是前端早读课第一发Angular类似的文章吧,不知道现在有多少人在使用这个框架呢?但今天分享的内容相信会使有这块经验的人理解的更加清楚。
正文从这开始~
首先做一下自我介绍,之前在腾讯呆过三年,所在团队是AlloyTeam,做的是WebQQ, 手机QQ相关的产品。现在在广发证券,担任前端技术专家,主要做的是易淘金,大数据日志平台、金钥匙相关的东西。
今天主题的大纲,总共分三部分:
Angular2概述
Angular2 (下文也称为ng2) 的基础介绍与ES6, TypeScript。
Angular2 基本介绍
Angular1是在09年推出的,而14年宣布开发Angular2,从开发时间线可以看出1跟2是同时在开发的,Angular 1.5推出component,旨在帮助开发者更好地迁移到Angular2。今天已经到了Angular2推出了Beta11,并且有Build版本,issue里程碑已经往正式版侯选方案走,而且进展是非常快的,可以猜想到Angular2将在这几个月内发布。
在谷歌搜索热度图中,可以看出React在整个世界范围内火爆程度远远没有Angular1火爆,热度相差好几个等级。蓝色线是Angular2,在16年左右已经开始在上涨,目前Github的star数已达上万,依据Angular1,也可以预见Angular2日后的火爆程度。
Angular2 新东西
首先是性能的提升,包括启动时间的加快;另外是跨平台,可以在支持服务端渲染,支持webworker等;移动先行原则,支持安卓4.1,ios7.1以上;可灵活选用不同开发语言版本,包括支持ES5、TypeScript(默认的)、Dart。另外针对项目体积庞大的问题,也有一个优化的计划,力求将写一个Hello World的负载减少压缩到10KB以下。
用Angular2来写个输出今天日期的例子
Angular1是用两个花括号跟竖线做过滤,Dart版本是这样,而Angular2是这样写的,大家发现区别了呢?答案是没有区别的。这也是说明了为什么它叫Angular2,而不是其它名字。它确实是基于Angular1的版本改版而来的,并且很多理念内容也都适配了Angular1。
我一开始企图想用一个例子把Angular2宣布新特性囊括进去,但是发现很难,因为Angular2的语法特性实在是很多的,这个例子写了模块,路由,组件,模板等语法,包括了百分之六七十的Angular2的东西。
Angular2的一些基石
首先是ES6,去年6月已经发布了。看一下浏览器程度,这里有很多绿色,绿色虽然是券商不太好意头的颜色,但也说明支持程度已经很高了,包括OS9已经达到54%,chrome51到达了97%的支持度,说明这个标准已经是被广大浏览器厂商认同推行的。
四个ES6重点新特性
第一个是模块,import与export, 类似于python中的模块,以前也有CommonJS规范,但ES6的模块可以解决类似循环依赖的问题,等到真正依赖的地方再进行依赖加载。另外浏览器原生模块跟HTTP2的结合,速度将变得非常快,是一种更优的按需加载的形式。示例中有个注意的地方,不同于上面的点杠,”angular2/core”不根据路径走的。右下角是实际项目里的一段代码,通过webpack配置模块的别名,不需要每个地方写…/…/…/,这种相对路径容易出错,只需要写别名,即可自动映射相应路径。
第二个特性是Classes,类这个东西是面向对象加了很多新支持的语法,这里具体不展开,更多可以看一下demo。另外在TypeScript中也对ES6的类进行扩充,包括加入了类属性编写,类型限制等新东西。
第三个是字符串模块,它可以支持多行字符串,字符串的插值还有里面的引号不需要转义。这里注意什么细节点呢?模块字符串里面认为这些空格都是真实的空格,而不是排版的内容,所以导致输出的字符串中有多个空格,根据这些ES6的坑我们在Github上推出了ES6Coding Style,重点讲讲怎么规范写法,以及要注意哪些坑。
第四个是箭头函数,大家可以看一下这段代码如果用ES5会变成什么样。可以看到ES5比ES6多了很多代码,这也意味着用ES6或者TypeScript可以减少更多开发时间。还有一个注意点,在严格模式下,模块顶层的this是undefined,所以箭头函数指向顶层的话,此时this就是undefined。更多注意点可以关注ES6 Coding Style。
Angular 2中谈论ES6时,我们在谈论什么?
实际我们谈的是TypeScript。
TypeScript是ES6的超集,可以理解为是Type加上javascript,使得JS更像Java。关于TS的类型限定修饰可以看看下面的例子。在TS中另一个比较重要的东西是Decorator - 修饰器,这东西就像这个例子一样,作用是对一个函数进行一些修改,也就是修饰。修饰器起修改作用是发生在编译时而不是在运行时。
另外只能修饰类跟类的方法,不能修饰function,因为function存在提升的问题,如果直接写实际会被提到最顶部,导致修饰不了。另外它是跟@对应的东西是AtScript里面的Annotation– 注解。现在没有AtScript东西了,annotation并不是标准,类似于框架自己说了算的东西,而Decorator是ECMAScript标准。TypeScript内置实现了Angular 2 里面的annotation的东西,但此时我们还是称之为Decorator。
Angular2里面重要的Decorator
最重要的是@Component还有@RouteConfig,可以配置路由的。这是从14年到16年的进展,蓝色是TypeScript,15年呈现指数爆炸的增长趋势,这个时间点是因为Angular2选用了TypeScript导致热度持续上涨。
Angular2核心剖析
讲讲整个新的Angular到底有哪些东西,包括可使用的工具和怎么升级。
Angular2是怎么跑起来的
中间是组件,通过Metadata元数据修饰它,组件能跟模板进行结合,通过属性跟事件绑定做上下数据传播,右边是指令,它也有自己的元数据进行修饰,指令可以注入到模版里面。左边的服务可以被依赖注入到组件里,还有其它模块可以被import进来使用的,这是整个基本外层的大架构。
Angular2是组件化的,它遵循分而治之的思想。把大的网站当成积木的组装,如果需要什么东西我就引入进来组装上去,一块块地组装完成自己所需要的模型,这是组件化开发思想的理念。掌握了组件相关的内容,接可以写很多Angular2的东西了。
下面的例子主要是第一部分讲到的一些ES6跟TS的写法。
传入修饰器里面的这个对象叫做元数组,主要是对修饰器进行相关配置的功能。组件跟组件之间也可以互相嵌套引用的。
组件树的工作机制
组件树可以通过属性绑定@Input将数据从根组件传给子组件,通过事件绑定@Output把数据传回给根组件,形成上下数据传播的机制。右上角是在模版里嵌的子的组件,怎么跟父组件联通的原理图。上下流动的机制保证组件之间可以相互通信。
组件的生命周期
这里罗列了Angular2支持的生命周期钩子,ng2运行到了哪个阶段都可以被你所操纵处理,右上角使用的代码,需要implements继承Hooks,然后在类中编写增加ng前缀的方法名,就可以对相应的生命周期进行操作处理。
这里的OnChanges一般搭配SimpleChange来取前后值,ng2也可以支持自定义变化检测DoCheck,但注意不能与changes同时存在。让我们再回到组件的例子。这里有一个东西providers,这是Angular2依赖注入,引入进来作为服务,可以在下面直接注入,并且使用它。
Angular2的依赖注入
依赖注入背后的理念是希望提供接口层的东西给调用方,而不只是实现层,相当于把可复用逻辑抽象成接口,调用时再将实例提供给其它的组件。
常用的依赖注入的场景是刚刚也讲到的服务。服务内部是怎么样的呢?在Angular2中每一个类都是可以被注入的,只需要写上@Injectable(),这是可注入的意思。注意一定要就写上这个修饰器,确保以后需要用到的时候不会出错。
依赖注入有哪些东西
首先服务也是可以全局注入的,但是我们不推荐,因为注入粒度不好控制,容易造成冗余。还有一种可能(可选)注入,某些注入服务并不需要用到,你可以加上@Optional()让它变成可选的。还有子注入器应用在实现非单例的情况下。
指令也可以互相注入使用,通过@ContentChildren。而关于提供的服务类型我们刚才讲到很多是类,它也支持使用值和工厂方法。
template这个地方是组件里面的模板,我推荐你们把模板写成单个文件,通过templateUrl的形式引入进来,方便统一组织管理。另外我们刚刚只讲到了组件,没有讲到Angular1里面的指令,Angular2里也有指令,组件只是指令三种类型中最重要的一种,指令加上模板形成了组件。
模块里有什么
首先模块可以支持插值,我提供了这个代码,上面是Angular2内部的写法,下面是语法糖,这种双花括号实现了插值就是一种语法糖。另外是属性绑定,可以将表达式的值动态传输到属性上。这里关注一个点,Angular1中的双花括号的写法跟现在这种用中括号做属性绑定有什么区别联系呢?看了例子,通过<imgsrc={{picUrl}}>引入会有什么问题?
如果这么写的话一开始就会加载这张图片,并且这张图片是404,这个值一开始加载的时候Angular并不知道这个值到底是什么,因为src是属性的默认行为,会读这个东西。在Angular2里面是通过中括号形式,这不是html的默认属性,不会有默认的加载行为,理念上是想把这些操作主动权交到开发者身上,自己控制到底想绑定是什么具体的东西。再有事件绑定,就是用小括号的写法。
数据流流动机制
关于这些绑定的数据流流动机制,组件树那部分也讲到了,就是往下流跟往上流的东西。这里有一个例子,讲到了inputs跟@input,这两者有啥区别呢?首先inputs是属于组件元数据的内容,我们知道元数据的东西都是这个修饰器内部去实现的,所以inputs并不需要从第三方引入进来,而@input 有个@,是个修饰器,需要从angular2/core里面import进来,两者的功能是一样的,但@input可用的场景比较多。
刚刚讲到的属性绑定跟事件绑定都是单向流动的,还有可以通过[(ng-model)]实现双向绑定。这里我们来看看它的基本实现原理是怎么样的。通过host,可以操纵宿主元素,一个组件最外层的就是宿主元素,通过属性与事件绑定,实现数据的上下自动流动,也就是双向的绑定。另外在表单Forms中,还有个重要的东西就是ngControl,它可以判断表单的状态变化还有进行有效性的检验。
过滤加工器
在模板中用的比较多的还有Pipe,我把它称为过滤加工器。实际上它的功能也就是对数据进行过滤展示,同时Pipe可以支持链式调用写法,而Angular2也内置了一些Pipes,详情可见angular2/common的API。当然你想自定义自己的Pipe也是支持的。
内置指令
Ng2支持NgClass, NgStyle, NgIf, NgSwitch, NgFor等内置指令。这里以NgIf举例,剖析一下内置指令怎么实现的。首先我们常用的是前面带*的写法,这实际上是个语法糖,是会被Angular2先转为template的写法,而Angular2内部会再去识别处理表达式语法,最后会变成template标签的一套东西,也是web component那套东西。再通过ngIf这个属性,判断到底展示不展示节点。
讲完NgIf,而在Angular 1中比较常用的还有ng-show, 那么在Angular 2中怎么实现呢?这里实现就是通过增加[hidden]属性进行控制,但这里有个坑,这个属性得IE10才能支持,而IE9用不了,Angular2声称是支持到IE9,如果想支持这个,那将需要进行兼容性处理。
另外关于Angular 1和2的对比,官方提供了更为具体的对比方案。这里提供了部分对比的截图,这对于有Angular1开发经验的人来理解Angular2是非常好的教材。
刚刚把Angular2三个最核心的概念讲完了,其他如路由,测试等等暂不细讲了,如果我刚刚讲的你们都掌握的话,基本可以写好Angular2的项目了。
工具相关的部分
刚刚讲到我们团队的ES6 codingstyle,写Angular2也需要自己的Style Guide,我们也讲到Angular2的一些特性是支持多种写法,在一个团队中我们更希望写的代码更规范化,统一风格的。另外有些编码注意点也会在风格指南讲到,例如不要直接操作DOM,而采用一些顶层封装的接口之类的。由这个Angular2 Style Guide也带来了ng2lint,它是一个代码质量检查工具,基于TSLint(检查TypeScript的代码质量) 改造而来。
还有是Debug工具,这里我们采用Batarangle,它是一个Chrome插件,可以对Angular2代码的结构进行分析调试。附带说明一下这个工具并不是Angular2官方提供的。而是之前Angular团队在Twitter上发公告想招募第三方团队来开发Angular 2的调试工具,Angular团队提供技术支持,这样他们能更专注核心代码的开发。另外我们广发证券前端团队也将参与Angular2官方开发文档的汉化工作中。
一些流行的React系相关的东东也可以在Angular 2中被使用。首先是热加载器Hot Loader。功能是修改了代码不用刷新浏览器,界面自动变化。还有Angular2与Redux, Relay的结合操作的库。
另外帮助快速上手的脚手架,我们无需关注太多冗余的搭建原理细节,搭建完就可以直接写代码。这里提供几个常用的脚手架还有我们广发证券自己写的脚手架。
怎么升级?
如果你的项目用了Angular1怎么升到Angular2这里不具体展开讲,因为可能很多人并不一开始用Angular1的,Angular2官方也提供了很详细的文档,包括准备工作,怎么用适配器进行更新。当然你也可以混合Angular1跟Angular2代码,通过适配器将Angular1跟Angular2混合地写在一起,但是我并不太推荐这种做法。
我这里也提供了一些解决方案,包括我之前也分享过ES6化的Angular1.X,你可以更先熟悉ES6,如果ES6用得很熟,写起Angular2是更加容易的。更多的升级内容可以看看我提供的相关文章。
延伸扩展
延伸部分,深入Angular 2的一些机理和强大的地方。
Change Detection
变化检测,我们想一个问题,什么情况下会造成界面的变化?
这里归纳为三种情况,首先是事件,例如点击事件,触发回调就可能造成界面的变化,第二种是通过XHR获取数据,数据返回后也可以造成新的变化。第三种setTimeout之类的,到指定时间再去执行相关代码,也可以发生新变化。
再来想一下这三种东西有什么共同点?
答案就是都是异步的,异步拥有未知的时序情况,所以在某个特定的时间可以发生新的变化。
那么发生了变化,我们怎么通知框架进行下一步处理呢?在Angular2里面怎么做的呢?
答案就是标题里的Zone.js。Zone.js是一个独立的第三方库,是Dart语言的一个特性。这里举个例子来理解一下它。下面的函数中有setTimeout,那就可以发生变化,假如我们用zone进行包装,即加上zone.run()。
变化就可以被Zone捕获,并且知道什么时候执行,而此时的setTimeout已不再是BOM层面的东西,而是Zone内部实现的Zone.setTimeout,这些称为monkey-patched(内置对象的扩展),类似的还有addEventListener, alert等等。另外被zone包装过后还可以再进行加工处理,例如beforeTask在任务之前做预处理之类的。具体内容可以看官网的介绍。
而Zone跟ng2也是有结合的版本,就是NGZone,实际上只是在Zone.js的基础上扩展多几个方法,包括检测变化的开始跟结束,并且这些方法是基于Observables的,写法是下面这个例子,可以检测订阅变化,并且通知Angular2的变化,做相应的修改。
ng2内部是如何变化的
下面是一张组件树的图,如果孙节点发生了变化,这时候Angular2会怎么处理呢?
答案是会从组件树的头到尾逐步判断哪里发生变化,然后进行相应的处理,是自上而下。这个速度快不快呢?我们肉眼感觉可能不是特别快,但是Angular2官方的说法是根据不同平台可以几毫米完成成千上百次的检查,当然我们可以写面向VM优化的代码,使得速度更加快。
刚刚也提到这种从上而下直接渲染好像不是特别快的感觉,接下来我们来看一个问题,我从Angular 2 issue上截的图,有个中国人提了个问题,服务器推送的对象只改了某一个属性,但他觉得有点慢,并不想发生全部的更新渲染。官方给的答案是:不好意思,我们看不懂中文
那么有没有解决方案呢,答案是肯定的。接下来看看一些更智能的变化检测,首先是Immutable data - 不可变数据。它有个由来是之前clojure社区写了自己的react版本OM发现性能比原版的react要好很多。究其原因发现是immutable data带来的好处,因此就有人将它抽离成单独的js库。
刚刚讲到如果是数据是可变的,从头到尾进行判断处理,如果是不可变的数据,意味着是只依赖于input属性变化的机制。我们给左边的子组件加上onPush当右边孙组件发送变化时,就只有右边会发生变化。
第二种智能检测变化的手段是采用Observables进行订阅收发事件。下面例子里有ChangeDetectorRef.markForCheck(),可以标示出改变的路径,其他无关的路径,Angular2将不进行处理。时间复杂度从O(n)降为了O(logN)。这两种智能检测的手段是可以混合嵌套使用的。实际项目中并不用每一处都这么写,除非遇到性能瓶颈才考虑这些手段,因为ng2本身检测的速度也是非常快了,而智能检测的写法比较费时也考功夫。
响应式编程
它背后的理念是订阅或者观察者模式,并且在Angular2HTTP CLIENT里实现的就是这套东西。在响应式编程的世界里全部都是流,我们可以在流的基础上进行加工处理,这里跟Gulp的理念是接近的。这里实际上响应式编程出现得要比Gulp早很多,但也侧面说明在前端社区并不特别流行。其中Rx.js就是在前端社区里实现响应式编程比较流行的库。
接着想想它跟promise有什么区别呢?
实际上它是包括了promise,在这基础上加了事件的东西,并且加入了其它内置的操作方法,包括可以定义方法什么时候触发,或者缓存相同的搜索结果,都只需要写一个方法就可以了。
同时可以处理多个无序的请求的操作,这里的例子用的是switchMap,它可以支持自动退订,我们发的多个请求之间是无序的,我们不知道谁会先返回数据,但如果是自动退订,返回回来会自动退掉之前订阅的,用最新的值。这些东西已经内置的,我们并不需要手动实现它们,这就是它的好处,让你更专注于自己的业务代码。更多响应式编程的内容可以看我提供的资料。
WebWorkers现在也是可被Angular2支持的,它的一个好处是可以把UI跟Worker线程进行分离,在计算非常庞大的表的时候可以优先确保展示UI界面出来,再通过worker进行CPU密集的计算任务。Angular 2支持的本质依然是Worker那套东西,不能访问DOM,不能跟UI共享内存等问题还是在那。怎么用呢?
这里我放了个例子,可以看到worker平台部署相关的是从angular/platform/worker_render里面引入的,不同于web平台是从angular2/platform/browser引入bootstrap的,也说明了Angular2是支持多平台的使用的。在平常开发中比较少用到这套东西,更深入的内容可以看看官方Github的一些介绍。
服务端渲染
在Angular1里面是不支持的,现在Angular2支持了服务端渲染。Angular2官方的服务端渲染项目叫做Universal。它可以把渲染逻辑处理放到了服务端去处理,支持选择输出浏览器的DOM或者服务端的DOM字符串。这里的判断展示的实际是依靠preboot去进行控制处理。
一些要注意的事项,包括不直接操作原生的元素,用Render或其他封装的接口进行处理;尽量让指令无状态化;还有用选取支持的DOM,它并不是全部DOM的。
问答
提问:我一直没有用过Angular,了解也不是很深,Angular2这种东西是用在了什么类型项目里面,有什么样的适用场景?
汤桂川:感谢这位同学。我之前在腾讯内部会用Angular1,这里考虑到一些问题,首先浏览器支持的情况,腾讯需要支持亿万用户,不会用到公众平台,当然你不需要兼容旧版本浏览器那还是可以试试的,或者想快速搭建一个网站,特别是spa的单页网站,它的好处写起来很方便很快。而Angular2的一个好处把近三年的东西融在一起,通过学习这个框架可以学习更多前端最前沿的东西,对你的学习成长也是帮助。Ng2适用场景的话,更多的还是跟ng1接近。需要快速成型,中大型项目用的话效果比较好。像我们很多项目都用,因为我们不会关注太多的兼容问题。
提问:我们现在是Angular重度用户,几十个页面都是用Angular构造的,现在Angular有发新版,完全升级相当于要把之前所有页面重构一遍,跟开发一个新项目一样成本很高,如何降低升级的成本?
汤桂川:这个问题之前也遇到了,首先一个点现在是BETA11,ng2更新的速度非常快,还是要先等正式版出来才考虑迁移的问题。我刚也讲到在QCon分享过ES6化的ng1,我们内部主推用ES6的东西,在ng 1.4,1.5版本上转换为ES6模式的写法,将来升级到ng 2是可以接近无缝,因为很多语法是接近的。包括官方很多提供的支持,可以先把ng1升级过渡到1.5,另外ng2也支持混合ng1和2的写法,通过适配器兼容。另外是考虑一些公用模块的抽离复用。你项目比较大的话,我个人觉得不要轻易试错,我们是在一些新项目里才会大面积得使用ng2,重写的成本确实挺高的,应该要谨慎一些。
后语
@汤桂川所服务的广发证券最近在招聘前端开发,有兴趣的可以来这看看,
关于本文
原文:http://dwz.cn/33aWOL
以上是关于第543期Angular 2 核心模块剖析的主要内容,如果未能解决你的问题,请参考以下文章
MMdetection框架速成系列 第03部分:简述整体构建细节与模块+训练测试模块流程剖析+深入解析代码模块与核心实现
Angular2-signaturepad 与 Angular 一起使用会导致找不到模块错误
在 angular.bootstrap 之后添加 Angular 模块