Angular 开发者常犯的错误
本文基于 Top Common Mistakes of Angular Developers 这篇文章的内容进行整理和扩展,建议有兴趣的读者直接阅读原文。如果你刚接触 Angular,也可以参考一下 Angular 常见问题汇总 这篇文章。 Angular vs Angular 2 vs Angular 4Angular 1.x 版本统称为 AngularJS,Angular 2+ (4/5) 统称为 Angular。 第三方库的命名也有一定的规则。假设早期版本的命名以 ngOnChanges vs ngDoCheckAngularJS 使用 Angular 移除了 // JS has NaN !== NaN export function looseIdentical(a: any,b: any): boolean { return a === b || typeof a === 'number' && typeof b === 'number' && isNaN(a) && isNaN(b); } 许多开发人员不知道这一点,陷入这个陷阱。为了解决这个问题,有各种解决方案:
使用
// packages/core/src/view/provider.ts // 变化检测: // checkAndUpdateView -> Services.updateDirectives(view,CheckType.CheckAndUpdate) function checkAndUpdateDirectiveInline( view: ViewData,def: NodeDef,v0: any,v1: any,v2: any,v3: any,v4: any,v5: any,v6: any,v7: any,v8: any,v9: any): boolean { const providerData = asProviderData(view,def.index); const directive = providerData.instance; let changed = false; let changes: SimpleChanges = undefined !; const bindLen = def.bindings.length; // 判断输入属性值是否改变,若发生改变则更新changes对象相应的属性。 if (bindLen > 0 && checkBinding(view,def,v0)) { changed = true; changes = updateProp(view,providerData,v0,changes); } // ... if (bindLen > 9 && checkBinding(view,9,v9)) { changed = true; changes = updateProp(view,v9,changes); } // 若输入属性发生变化才会调用ngOnChanges生命周期钩子 if (changes) { directive.ngOnChanges(changes); } // 若首次执行变化检测及实现OnInit生命周期钩子,则调用ngOnInit生命周期钩子 if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) { directive.ngOnInit(); } // 若实现DoCheck接口,则调用ngDoCheck生命周期钩子 if (def.flags & NodeFlags.DoCheck) { directive.ngDoCheck(); } return changed; // 返回SimpleChanges对象 } 未及时释放资源你可能知道当你订阅 Observable 对象或设置事件监听时,在某个时间点,你需要执行取消订阅操作,进而释放操作系统的内存。否则,你的应用程序可能会出现内存泄露。 @Component({ ... }) export class HeroComponent implements OnInit,OnDestroy { heroForm: FormGroup; valueChanges$: Observable; ngOnInit() { this.valueChanges$ = this.heroForm.valueChanges.subscribe(...); } ngOnDestroy() { this.valueChanges$.unsubscribe(); } } 大多数情况下,当你在组件类中执行订阅操作,你可以在 额外取消订阅操作上面介绍了在某些场景下需要手动执行取消订阅操作,进而释放相应的资源。但有些场景下,无需我们开发者手动执行额外的取消订阅操作。因为在这些场景下,Angular 内部会自动执行取消订阅操作。比如,使用 @Component({ selector: 'heroes-garden',template: `<hero [hero]="heroes$ | async"></todos>` }) export class HeroesGardenComponent implements OnInit,OnDestroy { heroesChanged$: Observable; ngOnInit() { this.heroesChanged$ = this.store.select('heroes'); } ngOnDestroy() { this.heroesChanged$.unsubscribe(); } } 除了使用
若想进一步了解手动释放资源和自动释放资源的场景,可以参考专栏 Angular 中何时取消订阅 这篇文章。 @Component.providers vs @NgModule.providers分层依赖注入作为 Angular 的新机制的一部分,让我们可以灵活地控制依赖注入。在 AngularJS 中,服务都是单例的,而 Angular 2.x 以上的版本,我们可以多次实例化一个服务。 假设我们已经定义了一个 @Injectable() export class HeroesService { heroes: Hero[] = []; constructor(private http: Http) { this.http.get('http://give-me-heroes.com') .map(res => res.json()) .subscribe((heroes: Hero[]) => { this.heroes = heroes; }); } getHeroes() { return this.heroes; } } 正如你所见,我们在构造函数中获取英雄的数据,此外我们定义了 现在我们来使用刚创建的
@Component({ selector: 'hero',template: '...',providers: [HeroesService] }) export class HeroComponent { constructor(private heroesService: HeroesService) {} } @NgModule({ declarations: [HeroComponent] } export class HeroesModule { ... } 在 解决上述问题的一种方案是在
@Component({ selector: 'hero',template: '...' }) export class HeroComponent { constructor(private heroesService: HeroesService) {} } @NgModule({ declarations: [HeroComponent],providers: [HeroesService] } export class HeroesModule { ... } 采用这种方式的话,对于多个 直接操作 DOMAngular 不再是简单的 Web 框架,Angular 是一个平台,它的一个优点是允许我们将应用程序代码与渲染器分离,从而编写可以在浏览器、服务器上运行的应用程序,甚至可以编写原生应用。 此外解耦后,也为我们提供更多的能力,如使用 AOT (Ahead of time) 或 Web Worker。AOT 意味着在构建阶段进行模板编译,AOT 编译模式的开发流程:
除此之外 AOT 还有以下优点:
如果我们现在或将来要使用这种功能,我们需要遵守一定的规则。其中一个规则是不能使用 jQuery,document 对象或 ElementRef.nativeElement 来直接操作 DOM。具体示例如下: @Component({ ... }) export class HeroComponent { constructor(private _elementRef: ElementRef) {} doBadThings() { $('.bad-with-jquery').click(); this._elementRef.nativeElement.xyz = 'bad with native element'; document.getElementById('bad-with-document'); } } 如你所见, @Component({ ... }) export class HeroComponent { constructor( private _renderer2: Renderer2,private _elementRef: ElementRef) {} doGoodThings() { this._renderer2.setElementProperty(this._elementRef,'some-property',true); } } 上面代码中,我们通过依赖注入方式注入 渲染器是视图层的封装。当我们在浏览器中时,将使用默认渲染器。当应用程序在不同平台 (如 WebWorker ) 上运行时,渲染器将被替换为平台对应的渲染器。此渲染器需要实现 若想深入了解 Angular 渲染器,可以参考专栏 Angular Renderer (渲染器) 这篇文章。 多次声明同一个组件组件是 Angular 应用程序中的常见构建块。每个组件都需要在 在 Angular 中是不允许在多个模块中声明同一个组件,如果一个组件在多个模块中声明的话,那么 Angular 编译器将会抛出异常。例如: @Component({ selector: 'hero',}) export class HeroComponent { ... } @NgModule({ declarations: [HeroComponent] } export class HeroesModule { ... } @NgModule({ declarations: [HeroComponent] } export class AnotherModule { ... } 如你所见,
而对于其它情况,我们可以创建一个新的模块,如
NgModule({ declarations: [HeroComponent],exports: [HeroComponent] } export class SharedModule { ... } NgModule({ imports: [SharedModule] } export class HeroesModule { ... } @NgModule({ imports: [SharedModule] } export class AnotherModule { ... } 参考资源
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |