该文章的作者(我们正在翻译的第一部分)说,他已经在
Trade Me中从事大型Angular应用程序工作了大约两年了。 在过去的几年中,应用程序开发团队一直在代码质量和性能方面不断改进该项目。
这一系列材料将重点介绍Trade Me团队使用的开发方法,这些方法以关于技术(例如Angular,TypeScript,RxJS和@ ngrx / store)的二十多个建议的形式表达。 此外,还将关注通用编程技术,这些技术旨在使应用程序代码更简洁,更准确。
1.关于trackBy
使用
ngFor
遍历模板中的数组,将此结构与
trackBy
函数一起使用,该函数为每个元素返回唯一的标识符。
▍说明
当数组更改时,Angular重新渲染整个DOM树。 但是,如果您使用
trackBy
,则系统将知道哪个元素已更改,并将对DOM进行更改,这些更改仅适用于该特定元素。 可以在
这里找到有关此内容的详细信息。
▍到
<li *ngFor="let item of items;">{{ item }}</li>
▍之后
// <li *ngFor="let item of items; trackBy: trackByFn">{{ item }}</li> // trackByFn(index, item) { return item.id; // id, }
2.关键字const和let
如果要声明不打算更改其值的变量,请使用
const
关键字。
▍说明
适当使用
let
和
const
关键字可以阐明使用声明的实体的意图。 此外,这种方法更容易识别由于意外覆盖常数值而引起的问题。 在这种情况下,将引发编译错误。 另外,它提高了代码的可读性。
▍到
let car = 'ludicrous car'; let myCar = `My ${car}`; let yourCar = `Your ${car}; if (iHaveMoreThanOneCar) { myCar = `${myCar}s`; } if (youHaveMoreThanOneCar) { yourCar = `${youCar}s`; }
▍之后
// car , car const car = 'ludicrous car'; let myCar = `My ${car}`; let yourCar = `Your ${car}; if (iHaveMoreThanOneCar) { myCar = `${myCar}s`; } if (youHaveMoreThanOneCar) { yourCar = `${youCar}s`; }
3.输送机操作员
使用RxJS时,请使用流水线运算符。
▍说明
所传达的运算符支持树状摇动算法,即,导入它们时,仅计划执行的代码将包含在项目中。 这也使识别文件中未使用的语句变得容易。
请注意,此建议与Angular 5.5及更高版本有关。
▍到
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/take'; iAmAnObservable .map(value => value.item) .take(1);
▍之后
import { map, take } from 'rxjs/operators'; iAmAnObservable .pipe( map(value => value.item), take(1) );
4.隔离API修复程序
并非所有的API都是完全稳定且没有错误的。 因此,有时有必要在代码中引入一些逻辑以解决API问题。 与其在使用补丁API的组件中放置此逻辑,不如将其隔离在某个地方(例如,在服务中),并且从组件中引用不再是有问题的API,而是引用相应的服务。
▍说明
所提出的方法允许使校正保持“更接近” API,即,尽可能接近从其发出网络请求的代码。 结果,减少了与有问题的API交互的应用程序代码的数量。 另外,事实证明所有校正都在一个位置,因此更容易进行校正。 如果您必须修复API中的错误,那么在一个文件中执行此操作要比将这些更正散布到整个应用程序中要容易得多。 这不仅有助于创建更正,而且还有助于在项目中搜索适当的代码及其支持。
此外,您可以创建自己的标签,例如
API_FIX
(类似于
TODO
标签),并使用它们来标记更正。 这样可以更轻松地找到此类修复程序。
5.模板中的订阅
避免从组件中订购可观察物。 而是在模板中订阅它们。
▍说明
异步流水线操作员会自动取消订阅,从而简化了代码,无需进行手动订阅管理。 此外,这还降低了开发人员忘记退订该组件的风险,这可能导致内存泄漏。 使用林特规则来减少内存泄漏的可能性是可能的,这些规则旨在识别没有取消订阅的可观察对象。
另外,此方法的应用导致以下事实:这些组件不再是有状态的组件,当订阅之外的数据发生更改时,这可能导致错误。
▍到
// <p>{{ textToDisplay }}</p> // iAmAnObservable .pipe( map(value => value.item), takeUntil(this._destroyed$) ) .subscribe(item => this.textToDisplay = item);
▍之后
// <p>{{ textToDisplay$ | async }}</p> // this.textToDisplay$ = iAmAnObservable .pipe( map(value => value.item) );
6.删除订阅
订阅受监视对象时,请始终确保使用
take
,
takeUntil
等操作符正确删除对它们的订阅。
▍说明
如果您不取消订阅观察到的对象,这将导致内存泄漏,因为观察到的对象的流将保持打开状态,即使在销毁组件或用户转到应用程序的另一页之后,也有可能这样做。
更好的办法是创建一个linter规则来检测具有有效订阅的观察对象。
▍到
iAmAnObservable .pipe( map(value => value.item) ) .subscribe(item => this.textToDisplay = item);
▍之后
如果要观察某个对象的变化,直到观察到的另一个对象生成某个值,请使用
takeUntil
运算符:
private destroyed$ = new Subject(); public ngOnInit (): void { iAmAnObservable .pipe( map(value => value.item)
使用
this
是一种模式,用于控制对组件中许多观察到的对象的预订的删除。
如果只需要观察对象返回的第一个值,请使用
take
:
iAmAnObservable .pipe( map(value => value.item), take(1), takeUntil(this._destroyed$) ) .subscribe(item => this.textToDisplay = item);
请注意,这里我们将
takeUntil
与
take
一起
take
。 这样做是为了避免由于订阅直到导致组件销毁才导致获取值这一事实而导致的内存泄漏。 如果
takeUntil
未使用
takeUntil
函数,则预订将一直存在,直到接收到第一个值为止,但是由于该组件已经被销毁,因此永远不会收到该值,这将导致内存泄漏。
7.使用合适的运算符
将平滑运算符与可观察对象一起使用,应用与要解决的问题的特征相对应的对象。
- 当需要在新动作到达时忽略上一个计划的动作时,请使用
switchMap
。 - 如果需要并行处理所有调度的动作,请使用
mergeMap
。 - 需要按接收顺序依次处理动作时,请使用
concatMap
。 - 在处理以前收到的动作的过程中需要忽略新动作的情况下,请使用
exhaustMap
。
可以在
这里找到有关此内容的详细信息。
▍说明
如果可能,使用一个运算符,而不是通过将多个运算符组合成一个链来达到相同的效果,可以减少需要发送给用户的应用程序代码量。 使用错误选择的运算符可能导致错误的程序行为,因为不同的运算符对观察到的对象的处理方式不同。
8.延迟加载
然后,尽可能尝试组织Angular-application模块的延迟加载。 这种技术可以归结为这样的事实,即只有在使用某些东西时才加载它。 例如,仅在需要显示组件时才加载组件。
▍说明
延迟加载减少了用户必须下载的应用程序资料的大小。 由于未使用的模块不会从服务器传输到客户端,因此可以提高应用程序的下载速度。
▍到
// app.routing.ts { path: 'not-lazy-loaded', component: NotLazyLoadedComponent }
▍之后
// app.routing.ts { path: 'lazy-load', loadChildren: 'lazy-load.module#LazyLoadModule' } // lazy-load.module.ts import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { LazyLoadComponent } from './lazy-load.component'; @NgModule({ imports: [ CommonModule, RouterModule.forChild([ { path: '', component: LazyLoadComponent } ]) ], declarations: [ LazyLoadComponent ] }) export class LazyModule {}
9.关于其他订阅中的订阅
有时,要执行某些操作,可能需要来自多个可观察对象的数据。 在这种情况下,请避免在其他可观察对象的
subscribe
块内创建对此类对象的
subscribe
。 而是使用合适的运算符来链接命令。 在这样的运算符中,可以注意到
withLatestFrom
和
combineLatest
。 考虑这些示例,然后对其进行评论。
▍到
firstObservable$.pipe( take(1) ) .subscribe(firstValue => { secondObservable$.pipe( take(1) ) .subscribe(secondValue => { console.log(`Combined values are: ${firstValue} & ${secondValue}`); }); });
▍之后
firstObservable$.pipe( withLatestFrom(secondObservable$), first() ) .subscribe(([firstValue, secondValue]) => { console.log(`Combined values are: ${firstValue} & ${secondValue}`); });
▍说明
如果我们谈论可读性,代码的复杂性或不良代码的迹象,那么当程序没有充分使用RxJS功能时,这表明开发人员对RxJS API并不熟悉。 如果我们涉及性能主题,那么事实证明,如果可观察对象需要一些时间来初始化,它将订阅
firstObservable
,然后系统将等待操作完成,只有在第二个可观察对象之后才开始工作。 如果这些对象是网络请求,则看起来就像同步执行请求。
10.关于打字
始终尝试声明类型不是
any
变量或常量。
▍说明
如果在TypeScript中声明了变量或常量而未指定类型,则将根据分配给它的值来推断类型。 这可能会导致问题。 考虑类似情况下系统行为的经典示例:
const x = 1; const y = 'a'; const z = x + y; console.log(`Value of z is: ${z}` // Value of z is 1a
假定
y
是一个数字,但是我们的程序不知道它,因此它显示的内容看起来有问题,但不会产生任何错误消息。 通过为变量和常量分配适当的类型,可以避免类似的问题。
我们重写上面的示例:
const x: number = 1; const y: number = 'a'; const z: number = x + y; // : Type '"a"' is not assignable to type 'number'. const y:number
这有助于避免数据类型错误。
系统化的键入方法的另一个优点是,它简化了重构并减少了此过程中出错的可能性。
考虑一个例子:
public ngOnInit (): void { let myFlashObject = { name: 'My cool name', age: 'My cool age', loc: 'My cool location' } this.processObject(myFlashObject); } public processObject(myObject: any): void { console.log(`Name: ${myObject.name}`); console.log(`Age: ${myObject.age}`); console.log(`Location: ${myObject.loc}`); } // Name: My cool name Age: My cool age Location: My cool location
假设我们要在
myFlashObject
中将
loc
属性名称更改为
location
并
myFlashObject
编辑代码时出错:
public ngOnInit (): void { let myFlashObject = { name: 'My cool name', age: 'My cool age', location: 'My cool location' } this.processObject(myFlashObject); } public processObject(myObject: any): void { console.log(`Name: ${myObject.name}`); console.log(`Age: ${myObject.age}`); console.log(`Location: ${myObject.loc}`); } // Name: My cool name Age: My cool age Location: undefined
如果在创建
myFlashObject
对象时未使用类型输入,则在我们的情况下,系统会假定
myFlashObject
的
loc
属性的值
undefined
。 她认为
loc
可能不是无效的属性名称。
如果在描述
myFlashObject
对象时使用了键入,那么在类似的情况下,在编译代码时,我们会看到一条奇妙的错误消息:
type FlashObject = { name: string, age: string, location: string } public ngOnInit (): void { let myFlashObject: FlashObject = { name: 'My cool name', age: 'My cool age', // Type '{ name: string; age: string; loc: string; }' is not assignable to type 'FlashObjectType'. Object literal may only specify known properties, and 'loc' does not exist in type 'FlashObjectType'. loc: 'My cool location' } this.processObject(myFlashObject); } public processObject(myObject: FlashObject): void { console.log(`Name: ${myObject.name}`); console.log(`Age: ${myObject.age}`) // Property 'loc' does not exist on type 'FlashObjectType'. console.log(`Location: ${myObject.loc}`); }
如果您要开始一个新项目,则在
tsconfig.json
文件中设置
strict:true
选项将很有用,以启用严格类型检查。
11.关于短绒的使用
Tslint有各种标准规则,例如
no-any ,
no-magic-numbers ,
no-console 。 可以通过编辑
tslint.json
文件来自定义
tslint.json
,以便根据某些规则组织代码验证。
▍说明
使用短绒棉签检查代码意味着,如果规则中禁止的代码中出现某些内容,您将收到一条错误消息。 这有助于项目代码的一致性,提高其可读性。 在此处查看其他tslint规则。
应当指出的是,一些规则包括根据它们纠正不可接受的内容的手段。 如有必要,您可以创建自己的规则。 如果您有兴趣,请查看
此材料,
该材料讨论了使用
TSQuery为
短绒创建自定义规则。
▍到
public ngOnInit (): void { console.log('I am a naughty console log message'); console.warn('I am a naughty console warning message'); console.error('I am a naughty console error message'); }
▍之后
// tslint.json { "rules": { ....... "no-console": [ true, "log", // console.log "warn" // console.warn ] } } // ..component.ts public ngOnInit (): void { console.log('I am a naughty console log message'); console.warn('I am a naughty console warning message'); console.error('I am a naughty console error message'); } // . console.log and console.warn console.error, Calls to 'console.log' are not allowed. Calls to 'console.warn' are not allowed.
总结
今天,我们回顾了11条建议,希望对Angular开发人员有用。 下次,请再等11个提示。
亲爱的读者们! 您是否使用Angular框架来开发Web项目?
