所有你需要知道的关于Ivy,新的Angular引擎
由于官方文档并没有做Ivy的太多说明,出于好奇就有了这篇译文:)
原文链接: All you need to know about Ivy, The new Angular engine!
减少包的大小
现在让我们通过编辑tsconfig.app
来选择加入Ivy
。添加一节angularComplierOption
设置和enableivytrue
。对于新的Angular CLI项目,你可以在运行ng new
脚本时使用 --enableIvy
标志。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| { "extends": "../tsconfig.json", "compilerOptions": { "outDir": "../out-tsc/app", "types": [] }, "exclude": [ "test.ts", "**/*.spec.ts" ], "angularCompilerOptions": { "enableIvy": true } }
|
现在让我们再次使用ng build -prod
构建应用程序:
我们可以看到我们的包缩小了77KB,是包大小的15%,这意味着我们网站的加载时间提高了15%
工作原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import { Component } from '@angular/core';
@Component({ selector: 'app-root', template: ` <div> <span>{{title}}</span> <app-child *ngIf="show"></app-child> </div> `, styles: [] }) export class AppComponent { title = 'ivy-tree-shaking'; show: boolean; }
|
现在,让我们运行ngc
命令来生成转换后的代码与Ivy
生成的代码有很多变化:
- 我们不再有工厂
factory
文件,现在所有的装饰器都转换成了静态函数。在我们的示例中,@Component
转换为一个ngComponentDeffor
示例。
2.指令集已经改变,所以它可以被摇树优化了,并边小了
不仅仅是把包变小了
如果我们看看ngIf
指令编译后的代码:
1
| i0.ɵdid(4, 16384, null, 0, i3.NgIf, [i0.ViewContainerRef, i0.TemplateRef], { ngIf: [0, "ngIf"] }, null)],
|
由于某些原因,我的app组件与ViewContainerRef
与 TemplateRef
相关联。如果你想知道那两个来自哪里,它们实际上是NgIf指令实现的依赖项。
在Ivy
中,这变得更加简单,每个组件现在都引用子组件或指令,尽量使公共API更加独立。它的意思是当我们改变某物时,NgIf
的实现,我们不需要重新编译所有的东西,我们只需要重新编译·,而不是整个AppComponent类。
通过这种方式,我们不仅实现了更小的包,还实现了更快的编译,以及更容易地将库发送到NPM。
Debugging with Ivy
Ivy
还提供了更简单的调试API。
让我们创建一个Input
事件,并绑定到一个不存在的函数名为搜索:
1 2 3 4 5 6 7 8 9 10 11 12
| import { Component } from '@angular/core';
@Component({ selector: 'app-root', template: ` <input (input)="search($event)"> `, styles: [] }) export class AppComponent {
}
|
在用Ivy
之前,当我们尝试在输入框中输入一些内容时,我们会在控制台中看到:
ERROR TypeError: _co.search is not a function
有了Ivy,我们的控制台看起来会更有信息,告诉我们错误来自哪里:
ERROR TypeError: ctx.search is not a function
动态加载
Angular 8为加载模块带来了一个新的API,它现在支持ES6动态导入。
1 2 3 4 5 6 7
| const routes: Routes = [ { path: 'feature', loadChildren: () => import('./feature/feature.module') .then(({ FeatureModule }) => FeatureModule) } ];
|
提出问题:既然如此,为什么不直接在组件上尝试相同的导入呢?
1 2 3
| loadFeature() { import('./feature/feature/feature.component') .then(({FeatureComponent}) => FeatureComponent)
|
在Button上添加click
事件 loadFeature
居然可以工作! !等等,奇怪的事情发生了。我们加载了一个组件,但没有在模块中声明它。
那么,我们还应该在模块中声明组件吗?或者,模块现在是可选的吗?我们将很快回答这个问题,但是首先,让我们尝试将这个组件添加到视图中。
为此我们使用ɵrenderComponent
功能,对app.component做下修改:
1 2 3 4 5 6 7 8
| export class AppComponent { loadFeature() { import('./feature/feature/feature.component') .then(({ FeatureComponent }) => { ɵrenderComponent(FeatureComponent); }); } }
|
我在这里得到一个异常,这是有意义的,因为我们试图将组件附加到视图,但没有告诉谁是主机元素,对吗?
这里我们有两个选择,第一个是将FeatureComponent
选择器添加到DOM中,Angular会知道如何在选择器占位符上渲染组件:
1 2 3
| <button (click)="loadFeature()">Click Me</button> <app-feature></app-feature> <router-outlet></router-outlet>
|
或者renderComponent
有另一个签名,得到一个配置,我们可以设置主机。我们甚至可以添加一个不存在的主机,Ivy
会将它附加到它:
1 2 3 4 5 6
| loadFeature() { import('./feature/feature/feature.component') .then(({ FeatureComponent }) => { ɵrenderComponent(FeatureComponent, { host: 'my-container' }); }); }
|
模块仍然是必需的吗
正如我们刚刚看到的,我们不需要在模块上声明组件。这让我们所有人怀疑我们是否真的需要模块?
为了回答这个问题,让我们创建另一个用例-现在FeatureComponent
将注入一个配置,将在AppModule
中声明和提供:
1 2 3 4 5 6 7 8 9 10 11 12
| export const APP_NAME: InjectionToken<string> = new InjectionToken<string>('App Name'); @NgModule({ ..., providers: [ {provide: APP_NAME, useValue: 'Ivy'} ], bootstrap: [AppComponent] }) export class AppModule { }
|
FeatureComponent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { Component, OnInit, Inject } from '@angular/core'; import { APP_NAME } from 'src/app/app.module';
@Component({ selector: 'app-feature', template: ` <p> Hello from {{appName}}! </p> `, styleUrls: ['./feature.component.scss'] }) export class FeatureComponent implements OnInit {
constructor(@Inject(APP_NAME) public appName: string) { }
ngOnInit() { }
}
|
现在-如果我们试图再次加载组件,我们得到一个异常,因为我们的组件没有注入器:
没有在模块上声明组件也有缺点,我们实际上没有使用注入器。尽管如此,renderComponent
配置也让我们声明一个注入器:
app.component.ts
1 2 3 4 5 6 7 8 9
| export class AppComponent { constructor(private injector: Injector) {} loadFeature() { import('./feature/feature/feature.component') .then(({ FeatureComponent }) => { ɵrenderComponent(FeatureComponent, { host: 'my-container', injector: this.injector }); }); } }
|
结果是可以运行的!🎉🎉🎉
Higher Order Components (HOC)
正如我们刚刚看到的,Angular现在更加动态了,它还允许我们实现像HOC这样的高级概念。
什么是HOC
?
HOC是一个函数,它获取一个组件并返回一个组件,但同时也影响其中的组件。
让我们创建基本的HOC
通过添加它作为装饰到我们的AppComponent:
app.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import { Component, ɵrenderComponent, Injector } from '@angular/core';
@HOC() @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { constructor(private injector: Injector) { } loadFeature() { import('./feature/feature/feature.component') .then(({ FeatureComponent }) => { ɵrenderComponent(FeatureComponent, { host: 'my-container', injector: this.injector }); }); } }
export function HOC() { return (cmpType) => { const originalFactory = cmpType.ngComponentDef.factory; cmpType.ngComponentDef.factory = (...args) => { const cmp = originalFactory(...args); console.log(cmp); return cmp; }; }; }
|
现在让我们利用HOC
和动态导入的概念来创建一个惰性组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import { Component, ɵrenderComponent, Injector, ɵɵdirectiveInject, INJECTOR } from '@angular/core';
@LazyComponent({ path: './feature/feature/feature.component', component: 'FeatureComponent', host: 'my-container' }) @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { constructor(private injector: Injector) { } loadFeature() { import('./feature/feature/feature.component') .then(({ FeatureComponent }) => { ɵrenderComponent(FeatureComponent, { host: 'my-container', injector: this.injector }); }); }
afterViewLoad() { console.log('Lazy HOC loaded!'); } }
export function LazyComponent(config: { path: string, component: string, host: string }) { return (cmpType) => { const originalFactory = cmpType.ngComponentDef.factory; cmpType.ngComponentDef.factory = (...args) => { const cmp = originalFactory(...args);
const injector = ɵɵdirectiveInject(INJECTOR);
import(`${config.path}`).then(m => ɵrenderComponent(m[config.component], { host: config.host, injector }));
if (cmp.afterViewLoad) { cmp.afterViewLoad(); } return cmp; }; return cmpType; }; }
|
1.以下是一些有趣的话题:
如何在没有Angular DI的情况下获得注入器?还记得ngc命令吗?我用它来检查Angular是如何在转换后的文件中转换构造函数注入的,并找到了directiveInject
函数:
1 2
| const injector = new directiveInject(INJECTOR);
|
2.我使用HOC
函数创建了一个名为afterViewLoad
的新“生命周期”函数,如果它存在于原始组件上,它将在延迟组件呈现后被调用
接下来就能正常运行了。
总结
快速总结一下我们刚刚学到的:
Ivy,第三代Angular编译器真的来了!它具有向后兼容性,通过使用它,我们可以得到更小的包,更容易调试API,更快的编译和模块和组件的动态加载。
带有Ivy的Angular的未来看起来很激动人心,因为它拥有一些很酷很激动人心的特性,比如HOC。
Ivy还为Angular元素在我们的Angular应用程序中变得更加流行奠定了基础。
试试吧!这就像设置enableIvy标志为真一样简单!