Angular 2 目前仍处于 Alpha/Developer Preview 阶段,但主要功能和核心文档都已经 可用 。让我们在这里收集目前已知的关于 Angular 2 设计目标的信息,以及它们计划如何实现:
- Angular 2 的主要目标
- 推理更简单
- Angular 1 与 Angular 2 变化检测
- 使用区域更透明的内部结构
- 改进的堆栈跟踪
- 大大提高了性能(以及为什么)
- 改进的模块化
- 改进的依赖注入
- Web 组件友好(如何以及为什么)
- 支持 Shadow DOM
- 支持 Android 和 iO 中的 原生 移动渲染
- 支持服务端渲染
- 改进的可测试性
- 迁移到 Angular 2 的路径
- 结论
Angular 2 的主要目标
Angular 2 的主要目标是创建一个超级易学且有效的 Web 框架。让我们看看这是如何实现的:
目标:更容易推理
在当前版本的 Angular 中,我们有时不得不对某些用例的框架内部进行推理,例如必须对应用程序事件初始化和摘要周期进行推理:
- 在 Angular 1 中,没有 摘要循环完成 事件(请参阅新 Forms 模块的 原因 ),因为此类事件可能会触发进一步的更改,从而使摘要循环继续进行。
-
我们不得不考虑何时调用
$scope.apply
或$cope.digest
,这并不总是那么简单 -
有时我们不得不调用
$timeout
来让 Angular 完成它的摘要循环并只在 DOM 稳定时才做一些操作
为了让 Angular 2 更容易理解,目标之一是创建更透明的开箱即用的内部结构。
首先,让我们看一下 Angular 1 绑定机制是如何实现的,以及如何使它更加透明。
Angular 1 如何实现绑定
在 Angular 1 中,能够编辑表单并立即将这些更改反映在 Javascript POJO 上的
ng-model
功能是该框架变得如此流行的主要原因之一。
根据此 播客 (见 3:50),它在 Angular 1 中的工作方式如下:
-
在 Javascript 运行时,一切都可以通过设计进行修补——如果需要,我们可以更改
Number
类 -
Angular 在启动时会修补所有异步交互点:
- 超时
- Ajax 请求
- 浏览器事件
- 网络套接字等
- 在这些交互点,Angular 将对范围对象运行脏检查以查看是否发生了变化,如果发生变化则触发相应的观察者
- 重新跑dirty checking看是否有更多变化发生,重新跑watchers等。
Angular 1 中绑定工作原理的后果
结果是 DOM 与 POJO 永久保持同步并且它正常工作,但所有这些有时很难推理:
- 目前尚不清楚哪些观察者将被解雇,以何种顺序或多少次
- 模型更新的顺序很难推理和预测
- 摘要循环可以运行多次,这很耗时
Angular 团队在 Angular 2 的方向上采取的第一步是从 Angular 代码库中提取修补所有异步交互点的机制,并使其可重用。
介绍区域
这种重构的结果是 Zone.js ,它可以与 Java 中的线程本地上下文进行比较。
它可以用于许多用例,例如允许框架生成跨越多个 Javascript VM 轮次的长堆栈跟踪。
Angular 2 如何因区域而变得更加透明
Angular 2 使用区域机制使很多关于摘要循环的推理不再必要。如果由区域内的组件触发,这段普通的非 Angular 特定 Javascript 代码将透明地触发 Angular 2 摘要:
element.addEventListener('keyup', function () {
console.log('Key pressed.');
});
});
不再需要
$scope.apply
或
$scope.digest
,一切都透明地工作。在大多数用例中,我们可能不必对区域进行一般推理,并且仍然可以通过使用
VmTurnZone
在 Angular 区域外运行代码。
目标:提高性能
上面对摘要周期的描述清楚地表明,所有这些都可能很耗时,尽管在 Angular 1.3 和 Angular 1.4 中都进行了大量的性能改进。
但目前尚不清楚这些性能改进可以走得更远,原因之一是可能存在变化检测循环。
为了更好地理解性能提升是如何实现的(最后比 Angular 1 快 5-10 倍),最好参考这个 播客 和 博客文章 。我将尝试在这里总结 Angular 2 更快的两个主要原因:
更快地检查单个绑定
检查单个绑定的机制经过优化,允许 Javascript VM 通过即时编译将该代码优化为本机代码。不是递归地扫描对象树,而是在 Angular 启动时创建一个函数来查看绑定是否已更改。
这个绑定检查函数看起来像是我们手动编写的函数来测试变化,它可以很容易地被 VM 优化掉。
避免扫描组件树的各个部分
Angular 2 将允许开发人员为变更检测机制提供一些保证,以避免扫描组件树的某些部分。开发者基本上有两种选择:
- 使模型成为可观察对象:Angular 将检测到这一点并注册自己以观察模型。这样,如果模型发生变化/保持不变,Angular 将通过可观察机制获知,因此它不需要对该对象运行变化检测。
- 使模型不可变,例如使用 Facebook 的 immutable.js 。同样的想法是 Angular 将检测到这一点并避免在不可变对象上运行变化检测。
目标:改进模块化
在 Angular 1 中,Angular 模块主要是对相关功能进行分组的依赖注入容器。
例如,在第一次需要依赖项时,这些模块不会根据它们的依赖项列表进行异步加载,例如 AMD 模块。
Angular 1 和模块延迟加载
Angular 1 延迟加载仍然可以使用 ocLazyLoad 之类的解决方案,但理想情况下,它应该是框架原生的东西并且更透明,并且根据这个 播客 ,似乎在 Angular 2 中也是如此(见 13:06) .
作为前端包管理器对 npm 的改进
此外,一般而言,Angular 团队正在研究 改进 npm 以使其对前端更加友好,不仅对于 Angular,而且对于任何前端库。
Angular 2 并不强制这样做,但如果我们选择这样做,我们可以利用新的 ES6 模块加载器,这是一个标准的异步模块加载器,可以通过 es6-module-loader pollyfill 使用。
目标:改进依赖注入
Angular 1 依赖注入是构建更多模块化应用程序的飞跃,但它有几个极端情况,如果不进行重大破坏性更改就无法再修复。
Angular 1 有一个全局对象池
Angular 1 中的一种 DI 极端情况是每个应用程序只有一个全局对象池。这意味着如果例如主路由加载
backendService
并且我们导航到路由 B,我们可以在那里延迟加载特定于该路由的其他服务。
问题是,假设我们用完全不同的实现延迟加载第二个
backendService
:它会覆盖第一个!目前没有办法让两个服务名称相同但实现不同,这会阻止延迟加载以一种安全的方式在 Angular 1 中实现。
如果模块具有相同的名称,Angular 1 会悄悄地覆盖它们
这是一个允许例如在测试时用模拟替换服务层服务的功能,但如果我们不小心加载同一模块两次可能会导致问题。
Angular 1 中有多种 DI 机制
在 Angular 1 中,我们可以用不同的方式在多个地方注入依赖:
- 在链接功能中按位置
- 在指令定义中按名称
- 在控制器函数中按名称等
在 Angular 2 中,目标是将这些机制统一为一个机制,以减少学习曲线并提高可读性。
Angular 2 DI 将如何改善这种情况
在 Angular 2 中,只有一种 DI 机制:按类型注入构造函数。
element.addEventListener('keyup', function () {
console.log('Key pressed.');
});
});
只有一种机制这一事实使其更容易学习。此外,依赖注入器是分层的,这意味着在组件树的不同点可能有相同类型的不同实现。
如果组件没有定义依赖项,它将把查找委托给它的父注入器等等。这为在 Angular 2 中提供原生延迟加载支持奠定了基础。
目标:Web 组件友好
Web 的未来是 Web Components,而 Angular 2 从一开始就希望与未来的 Web 组件库兼容。为此,Angular 2 模板语法的目标之一是保持属性干净,不要在它们上面放置任何 Angular 表达式——一切都仅通过属性绑定。
要理解为什么这很重要,请看这个例子:
element.addEventListener('keyup', function () {
console.log('Key pressed.');
});
});
这里我们有一个 Angular 1 组件,它与未来的 Web 组件库交互。
这里有什么问题?
好吧,Web 组件的行为就像浏览器组件一样,例如具有
img
标签。
因此,在页面启动时和 Angular 有机会启动之前,Angular 表达式将传递给直接作用于它的组件,就像图像元素立即使用提供的 url 加载图像一样。
这实际上就是为什么需要像
ng-src
这样的所有属性来解决这个问题的原因。
Angular 2 如何更好地与 Web 组件交互?
在 Angular 2 中,模板语法将避免绑定到普通属性,除非读取常量:
element.addEventListener('keyup', function () {
console.log('Key pressed.');
});
});
[setting]
是一个属性绑定,它将表达式的值写入组件属性。任何地方都不会在普通属性中有 Angular 表达式,以防止与 Web 组件的互操作性问题。
支持 Shadow DOM
Web 组件的关键特性之一是
Shadow DOM
。这是一种本机浏览器机制,允许构建本机外观的组件,比如
select
的新实现。
Web 组件仍然可以在纯 HTML/CSS 中实现,但与主页隔离,在某些方面类似于在具有单独文档根的 iframe 中。
由于 Shadow DOM 目前仅在 Chrome 中实现,Angular 2 将通过 3 种不同的机制支持它:
- 默认模式:默认情况下,Shadow DOM 未打开,组件内部显示在与主页相同的文档树中
- 模拟 Shadow DOM:可以使用 Polymer 对 Shadow DOM CSS 隔离机制进行 pollyfiled,方法是在组件内动态添加 CSS 前缀,使 CSS 更加具体
- 真正的 Shadow DOM:如前所述,这仅适用于 Chrome
目标:支持 Native Mobile – iOS 和 Android
Angular 2 将有两层,应用层和渲染层。例如,一个组件可以使用不同的
@View
注释进行注释,这些注释可以在运行时根据环境启用。
与 React Native 类似,Angular 2 将允许支持以下概念:
一次学习,随处写作
这意味着可以重用 Angular 的知识来构建本机应用程序和 Web 应用程序,尽管总会有一些差异。
目标:支持服务器端渲染
支持服务器端渲染对于 SEO 和用户感知很重要:在大型 Angular 1 应用程序中,我们可以在启动过程中清楚地看到页面的引导过程,即使使用模板缓存预填充也是如此。
这是 Angular 2 中目前不太清楚的部分之一,但这个想法可能会像这样实现:
- 启动发生并且所有组件都被绑定
- 但渲染不会发生
- 页面在服务器中呈现并通过网络发送
- Angular 将解析它并将页面的一部分注入 DOM,避免闪烁效果
目标:提高可测试性
在 Angular 2 中,编写真正的单元测试相对困难,因为例如
ng-model
确实需要测试 DOM,这导致使用
PhantomJs
等解决方案。
这种方法的问题是那些测试不再是单元测试,它们是具有以下问题的集成测试:
- 他们执行起来很慢
- 它们很脆弱,更难维护
这就导致了 测试金字塔 的倒置,也就是我们大部分的测试要么是UI测试,要么是集成测试,几乎没有真正的单元测试。这意味着构建不断地因真正的错误以外的原因而中断,并且测试工作所发挥的作用比我们想要的要小。
引入单独的渲染层将使单元测试变得超快且依赖性最小,从而使它们更易于编写和维护,并允许更频繁地运行它们。
目标:迁移到 Angular 2 的路径
Angular 2 的目标之一是为 Angular 1 提供一条清晰的迁移路径。只有当 Angular 2 接近其初始版本时,这一点才会变得清晰,但目前路由器将成为该迁移的主要推动者之一。
新的 Angular 2 路由器正在向后移植到 Angular 1,并将允许同一个应用程序同时拥有 Angular 1 和 Angular 2 路由。
结论
我对 Angular 2 感到非常兴奋,在尝试了几个组件之后,我可以看到它如何更容易学习并且对开发人员来说更透明。同样,本文中描述的许多内容(例如区域)都可以正常工作。
与 3rd 方库的集成得到了很大改进,如果 npm 可以改进以对前端代码更有帮助,那将是巨大的。
想尝试一下吗?
尝试一下肯定不会太早,如果你想试一试,这是一个 种子项目 ,Visual Studio Code 编辑器 或 Webstorm 已经提供了很好的 Typescript 1.5 支持。