angular 最佳实践
by Vamsi Vempati
由Vamsi Vempati
干净高效的Angular应用程序的最佳实践 (Best practices for a clean and performant Angular application)
I have been working on a large scale Angular application at Trade Me, New Zealand for a couple of years now. Over the past few years, our team has been refining our application both in terms of coding standards and performance to make it be in its best possible state.
我已经在新西兰Trade Me从事大型Angular应用程序工作了两年了。 在过去的几年中,我们的团队一直在优化我们的应用程序的编码标准和性能,以使其处于最佳状态。
This article outlines the practices we use in our application and is related to Angular, Typescript, RxJs and @ngrx/store. We’ll also go through some general coding guidelines to help make the application cleaner.
本文概述了我们在应用程序中使用的实践,并与Angular,Typescript,RxJs和@ ngrx / store相关。 我们还将通过一些通用的编码准则来帮助使应用程序更整洁。
1)追踪 (1) trackBy)
When using ngFor
to loop over an array in templates, use it with a trackBy
function which will return an unique identifier for each item.
当使用ngFor
循环遍历模板中的数组时,将其与trackBy
函数一起使用,该函数将为每个项目返回唯一的标识符。
Why?
为什么?
When an array changes, Angular re-renders the whole DOM tree. But if you use trackBy
, Angular will know which element has changed and will only make DOM changes for that particular element.
当数组改变时,Angular重新渲染整个DOM树。 但是,如果使用trackBy
,Angular将知道哪个元素已更改,并且只会对该特定元素进行DOM更改。
For a detailed explanation on this, please refer to this article by Netanel Basal.
有关此内容的详细说明,请参阅Netanel Basal的 这篇文章 。
Before
之前
<li *ngFor="let item of items;">{{ item }}</li>
After
后
// in the template
<li *ngFor="let item of items; trackBy: trackByFn">{{ item }}</li>
// in the component
trackByFn(index, item) {
return item.id; // unique id corresponding to the item
}
2)const vs让 (2) const vs let)
When declaring variables, use const when the value is not going to be reassigned.
声明变量时,在不打算重新分配值时使用const。
Why?
为什么?
Using let
and const
where appropriate makes the intention of the declarations clearer. It will also help in identifying issues when a value is reassigned to a constant accidentally by throwing a compile time error. It also helps improve the readability of the code.
在适当的地方使用let
和const
可以使声明的意图更加清楚。 通过抛出编译时错误,当将值意外分配给常量时,这还将有助于识别问题。 它还有助于提高代码的可读性。
Before
之前
let car = 'ludicrous car';
let myCar = `My ${car}`;
let yourCar = `Your ${car};
if (iHaveMoreThanOneCar) {
myCar = `${myCar}s`;
}
if (youHaveMoreThanOneCar) {
yourCar = `${youCar}s`;
}
After
后
// the value of car is not reassigned, so we can make it a const
const car = 'ludicrous car';
let myCar = `My ${car}`;
let yourCar = `Your ${car};
if (iHaveMoreThanOneCar) {
myCar = `${myCar}s`;
}
if (youHaveMoreThanOneCar) {
yourCar = `${youCar}s`;
}
3)管道运算符 (3) Pipeable operators)
Use pipeable operators when using RxJs operators.
使用RxJs运算符时,请使用管道运算符。
Why?
为什么?
Pipeable operators are tree-shakeable meaning only the code we need to execute will be included when they are imported.
管道运算符是可摇树的,这意味着在导入时仅包含我们需要执行的代码。
This also makes it easy to identify unused operators in the files.
这也使识别文件中未使用的运算符变得容易。
Note: This needs Angular version 5.5+.
注意:这需要Angular 5.5+版本。
Before
之前
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/take';
iAmAnObservable
.map(value => value.item)
.take(1);
After
后
import { map, take } from 'rxjs/operators';
iAmAnObservable
.pipe(
map(value => value.item),
take(1)
);
4)隔离API骇客 (4) Isolate API hacks)
Not all APIs are bullet proof — sometimes we need to add some logic in the code to make up for bugs in the APIs. Instead of having the hacks in components where they are needed, it is better to isolate them in one place — like in a service and use the service from the component.
并非所有的API都是防弹的-有时我们需要在代码中添加一些逻辑以弥补API中的错误。 与其在需要它们的组件中进行黑客攻击,不如将它们隔离在一个地方,例如在服务中,然后从组件中使用该服务。
Why?
为什么?
This helps keep the hacks “closer to the API”, so as close to where the network request is made as possible. This way, less of your code is dealing with the un-hacked code. Also, it is one place where all the hacks live and it is easier to find them. When fixing the bugs in the APIs, it is easier to look for them in one file rather than looking for the hacks that could be spread across the codebase.
这有助于使黑客“更接近API”,从而尽可能地靠近发出网络请求的地方。 这样,更少的代码可以处理未经篡改的代码。 同样,这是所有黑客生活的地方,更容易找到它们。 修复API中的错误时,在一个文件中查找它们比查找可能分布在整个代码库中的黑客更容易。
You can also create custom tags like API_FIX similar to TODO and tag the fixes with it so it is easier to find.
您还可以创建类似于TODO的自定义标签(如API_FIX),并使用它来标记修订,以便于查找。
5)订阅模板 (5) Subscribe in template)
Avoid subscribing to observables from components and instead subscribe to the observables from the template.
避免从组件预订可观察对象,而从模板预订可观察对象。
Why?
为什么?
async
pipes unsubscribe themselves automatically and it makes the code simpler by eliminating the need to manually manage subscriptions. It also reduces the risk of accidentally forgetting to unsubscribe a subscription in the component, which would cause a memory leak. This risk can also be mitigated by using a lint rule to detect unsubscribed observables.
async
管道自动取消订阅,并且无需手动管理订阅,从而使代码更简单。 它还减少了意外忘记取消取消订阅该组件中的预订的风险,这将导致内存泄漏。 也可以通过使用棉绒规则检测未订阅的可观察物来减轻这种风险。
This also stops components from being stateful and introducing bugs where the data gets mutated outside of the subscription.
这也阻止了组件处于有状态状态,并引入了一些错误,在这些错误中数据在订阅之外发生了变异。
Before
之前
// // template
<p>{{ textToDisplay }}</p>
// component
iAmAnObservable
.pipe(
map(value => value.item),
takeUntil(this._destroyed$)
)
.subscribe(item => this.textToDisplay = item);
After
后
// template
<p>{{ textToDisplay$ | async }}</p>
// component
this.textToDisplay$ = iAmAnObservable
.pipe(
map(value => value.item)
);
6)清理订阅 (6) Clean up subscriptions)
When subscribing to observables, always make sure you unsubscribe from them appropriately by using operators like take
, takeUntil
, etc.
订阅可观察takeUntil
,请始终确保使用诸如take
, takeUntil
等运算符来适当地取消订阅。
Why?
为什么?
Failing to unsubscribe from observables will lead to unwanted memory leaks as the observable stream is left open, potentially even after a component has been destroyed / the user has navigated to another page.
如果取消打开可观察对象,则会导致不必要的内存泄漏,因为可观察流保持打开状态,甚至可能在组件已被破坏/用户导航到另一个页面之后也是如此。
Even better, make a lint rule for detecting observables that are not unsubscribed.
更好的是,制定一个检测未取消订阅的可观察对象的规则。
Before
之前
iAmAnObservable
.pipe(
map(value => value.item)
)
.subscribe(item => this.textToDisplay = item);
After
后
Using takeUntil
when you want to listen to the changes until another observable emits a value:
当您要监听更改直到另一个observable发出值之前,请使用takeUntil
:
private _destroyed$ = new Subject();
public ngOnInit (): void {
iAmAnObservable
.pipe(
map(value => value.item)
// We want to listen to iAmAnObservable until the component is destroyed,
takeUntil(this._destroyed$)
)
.subscribe(item => this.textToDisplay = item);
}
public ngOnDestroy (): void {
this._destroyed$.next();
this._destroyed$.complete();
}
Using a private subject like this is a pattern to manage unsubscribing many observables in the component.
使用这样的私有主题是管理取消订阅组件中许多可观察对象的模式。
Using take
when you want only the first value emitted by the observable:
当只需要由可观察对象发出的第一个值时,使用take
:
iAmAnObservable
.pipe(
map(value => value.item),
take(1),
takeUntil(this._destroyed$)
)
.subscribe(item => this.textToDisplay = item);
Note the usage of takeUntil
with take
here. This is to avoid memory leaks caused when the subscription hasn’t received a value before the component got destroyed. Without takeUntil
here, the subscription would still hang around until it gets the first value, but since the component has already gotten destroyed, it will never get a value — leading to a memory leak.
请在此处注意takeUntil
和take
的用法。 这是为了避免在销毁组件之前订阅未收到值时引起的内存泄漏。 如果没有takeUntil
,则订阅将一直徘徊直到获得第一个值,但是由于该组件已被销毁,因此它将永远不会获得值-导致内存泄漏。
7)使用适当的运算符 (7) Use appropriate operators)
When using flattening operators with your observables, use the appropriate operator for the situation.
当对您的观测值使用展平运算符时,请针对情况使用适当的运算符。
switchMap: when you want to ignore the previous emissions when there is a new emission
switchMap:当您要忽略先前的排放时有新的排放
mergeMap: when you want to concurrently handle all the emissions
mergeMap:当您要同时处理所有排放时
concatMap: when you want to handle the emissions one after the other as they are emitted
concatMap:当您要一个接一个地处理排放时
exhaustMap: when you want to cancel all the new emissions while processing a previous emisssion
exhaustMap:当您要在处理先前的排放时取消所有新排放时
For a more detailed explanation on this, please refer to this article by Nicholas Jamieson.
有关此内容的详细说明,请参阅Nicholas Jamieson的 这篇文章。
Why?
为什么?
Using a single operator when possible instead of chaining together multiple other operators to achieve the same effect can cause less code to be shipped to the user. Using the wrong operators can lead to unwanted behaviour, as different operators handle observables in different ways.
在可能的情况下使用单个运算符,而不是将多个其他运算符链接在一起以达到相同的效果,可以减少向用户交付的代码。 使用错误的运算符可能导致不良行为,因为不同的运算符以不同的方式处理可观察对象。
8)延迟加载 (8) Lazy load)
When possible, try to lazy load the modules in your Angular application. Lazy loading is when you load something only when it is used, for example, loading a component only when it is to be seen.
如果可能,请尝试将模块延迟加载到Angular应用程序中。 延迟加载是指仅在使用某项时才加载某项,例如,仅当其可见时才加载组件。
Why?
为什么?
This will reduce the size of the application to be loaded and can improve the application boot time by not loading the modules that are not used.
这样可以减少要加载的应用程序的大小,并且可以通过不加载不使用的模块来缩短应用程序的启动时间。
Before
之前
// app.routing.ts
{ path: 'not-lazy-loaded', component: NotLazyLoadedComponent }
After
后
// 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)避免在订阅中包含订阅 (9) Avoid having subscriptions inside subscriptions)
Sometimes you may want values from more than one observable to perform an action. In this case, avoid subscribing to one observable in the subscribe block of another observable. Instead, use appropriate chaining operators. Chaining operators run on observables from the operator before them. Some chaining operators are: withLatestFrom
, combineLatest
, etc.
有时,您可能希望从多个观察值中执行一项操作。 在这种情况下,避免在另一个可观察项的订阅块中订阅一个可观察项。 而是使用适当的链接运算符。 链接运算符在运算符之前的可观察对象上运行。 一些链接运算符是: withLatestFrom
, combineLatest
等。
Before
之前
firstObservable$.pipe(
take(1)
)
.subscribe(firstValue => {
secondObservable$.pipe(
take(1)
)
.subscribe(secondValue => {
console.log(`Combined values are: ${firstValue} & ${secondValue}`);
});
});
After
后
firstObservable$.pipe(
withLatestFrom(secondObservable$),
first()
)
.subscribe(([firstValue, secondValue]) => {
console.log(`Combined values are: ${firstValue} & ${secondValue}`);
});
Why?
为什么?
Code smell/readability/complexity: Not using RxJs to its full extent, suggests developer is not familiar with the RxJs API surface area.
代码气味/可读性/复杂性 :并未完全使用RxJ,这表明开发人员不熟悉RxJs API表面积。
Performance: If the observables are cold, it will subscribe to firstObservable, wait for it to complete, THEN start the second observable’s work. If these were network requests it would show as synchronous/waterfall.
性能 :如果可观察对象很冷,它将订阅firstObservable,等待其完成,然后开始第二个可观察对象的工作。 如果这些是网络请求,它将显示为同步/瀑布。
10)避免任何事情; 键入所有内容; (10) Avoid any; type everything;)
Always declare variables or constants with a type other than any
.
始终声明类型不是any
变量或常量。
Why?
为什么?
When declaring variables or constants in Typescript without a typing, the typing of the variable/constant will be deduced by the value that gets assigned to it. This will cause unintended problems. One classic example is:
当在Typescript中声明变量或常量而不进行类型输入时,变量/常量的类型将由为其分配的值来推导。 这将导致意外的问题。 一个经典的例子是:
const x = 1;
const y = 'a';
const z = x + y;
console.log(`Value of z is: ${z}`
// Output
Value of z is 1a
This can cause unwanted problems when you expect y to be a number too. These problems can be avoided by typing the variables appropriately.
当您也期望y为数字时,这可能会导致不必要的问题。 可以通过适当键入变量来避免这些问题。
const x: number = 1;
const y: number = 'a';
const z: number = x + y;
// This will give a compile error saying:
Type '"a"' is not assignable to type 'number'.
const y:number
This way, we can avoid bugs caused by missing types.
这样,我们可以避免由于缺少类型而导致的错误。
Another advantage of having good typings in your application is that it makes refactoring easier and safer.
在应用程序中进行良好键入的另一个优点是,它使重构更容易,更安全。
Consider this example:
考虑以下示例:
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}`);
}
// Output
Name: My cool name
Age: My cool age
Location: My cool location
Let us say, we want to rename the property loc
to location
in myFlashObject
:
假设我们想将属性loc
重命名为myFlashObject
location
:
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}`);
}
// Output
Name: My cool name
Age: My cool age
Location: undefined
If we do not have a typing on myFlashObject
, it thinks that the property loc
on myFlashObject
is just undefined rather than that it is not a valid property.
如果我们没有在打字myFlashObject
,它认为该财产loc
上myFlashObject
仅仅是不确定的,而不是它是不是有效的属性。
If we had a typing for myFlashObject
, we would get a nice compile time error as shown below:
如果我们输入myFlashObject
,则会得到一个不错的编译时错误,如下所示:
type FlashObject = {
name: string,
age: string,
location: string
}
public ngOnInit (): void {
let myFlashObject: FlashObject = {
name: 'My cool name',
age: 'My cool age',
// Compilation error
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}`)
// Compilation error
Property 'loc' does not exist on type 'FlashObjectType'.
console.log(`Location: ${myObject.loc}`);
}
If you are starting a new project, it is worth setting strict:true
in the tsconfig.json
file to enable all strict type checking options.
如果您要开始一个新项目,则值得在tsconfig.json
文件中设置strict:true
以启用所有严格类型检查选项。
11)利用皮棉规则 (11) Make use of lint rules)
tslint
has various options built in already like no-any
, no-magic-numbers
, no-console
, etc that you can configure in your tslint.json
to enforce certain rules in your code base.
tslint
已经内置了各种选项,例如no-any
, no-magic-numbers
, no-console
等,您可以在tslint.json
进行配置,以在代码库中强制执行某些规则。
Why?
为什么?
Having lint rules in place means that you will get a nice error when you are doing something that you should not be. This will enforce consistency in your application and readability. Please refer here for more rules that you can configure.
设置棉绒规则意味着您在做不该做的事情时会得到一个不错的错误。 这将增强应用程序的一致性和可读性。 请参考此处以了解更多可以配置的规则。
Some lint rules even come with fixes to resolve the lint error. If you want to configure your own custom lint rule, you can do that too. Please refer to this article by Craig Spence on how to write your own custom lint rules using TSQuery.
一些棉绒规则甚至附带解决棉绒错误的修复程序。 如果要配置自己的自定义棉绒规则,也可以这样做。 请参考Craig Spence的 这篇文章 ,了解如何使用TSQuery编写自己的自定义棉绒规则。
Before
之前
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');
}
// Output
No errors, prints the below on console window:
I am a naughty console message
I am a naughty console warning message
I am a naughty console error message
After
后
// tslint.json
{
"rules": {
.......
"no-console": [
true,
"log", // no console.log allowed
"warn" // no console.warn allowed
]
}
}
// ..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');
}
// Output
Lint errors for console.log and console.warn statements and no error for console.error as it is not mentioned in the config
Calls to 'console.log' are not allowed.
Calls to 'console.warn' are not allowed.
12)小型可重复使用的组件 (12) Small reusable components)
Extract the pieces that can be reused in a component and make it a new one. Make the component as dumb as possible, as this will make it work in more scenarios. Making a component dumb means that the component does not have any special logic in it and operates purely based on the inputs and outputs provided to it.
提取可在组件中重复使用的片段,并使其成为新的片段。 使组件尽可能的笨拙,因为这将使其在更多情况下都能正常工作。 使组件变得愚蠢意味着该组件中没有任何特殊逻辑,并且仅基于提供给它的输入和输出进行操作。
As a general rule, the last child in the component tree will be the dumbest of all.
通常,组件树中的最后一个子节点将是最笨的。
Why?
为什么?
Reusable components reduce duplication of code therefore making it easier to maintain and make changes.
可重用的组件减少了代码重复,因此使维护和更改变得更加容易。
Dumb components are simpler, so they are less likely to have bugs. Dumb components make you think harder about the public component API, and help sniff out mixed concerns.
笨拙的组件更简单,因此它们不太可能出现错误。 愚蠢的组件使您对公共组件API的思考更加艰辛,并帮助您找出混杂的问题。
13)组件只能处理显示逻辑 (13) Components should only deal with display logic)
Avoid having any logic other than display logic in your component whenever you can and make the component deal only with the display logic.
尽可能避免在组件中使用除显示逻辑以外的任何逻辑,并使组件仅处理显示逻辑。
Why?
为什么?
Components are designed for presentational purposes and control what the view should do. Any business logic should be extracted into its own methods/services where appropriate, separating business logic from view logic.
组件设计用于演示目的并控制视图应执行的操作。 任何业务逻辑都应酌情提取到其自己的方法/服务中,以将业务逻辑与视图逻辑分开。
Business logic is usually easier to unit test when extracted out to a service, and can be reused by any other components that need the same business logic applied.
当提取到服务中时,业务逻辑通常更易于进行单元测试,并且可以由需要应用相同业务逻辑的任何其他组件重用。
14)避免冗长的方法 (14) Avoid long methods)
Long methods generally indicate that they are doing too many things. Try to use the Single Responsibility Principle. The method itself as a whole might be doing one thing, but inside it, there are a few other operations that could be happening. We can extract those methods into their own method and make them do one thing each and use them instead.
长方法通常表明他们在做太多事情。 尝试使用单一责任原则。 整个方法本身可能正在做一件事,但是在内部,可能还会发生其他一些操作。 我们可以将这些方法提取到它们自己的方法中,并使它们各自执行一项操作,而改为使用它们。
Why?
为什么?
Long methods are hard to read, understand and maintain. They are also prone to bugs, as changing one thing might affect a lot of other things in that method. They also make refactoring (which is a key thing in any application) difficult.
长方法很难阅读,理解和维护。 它们也容易出错,因为更改一件事可能会影响该方法中的许多其他事情。 它们也使重构(这在任何应用程序中都很关键)变得困难。
This is sometimes measured as “cyclomatic complexity”. There are also some TSLint rules to detect cyclomatic/cognitive complexity, which you could use in your project to avoid bugs and detect code smells and maintainability issues.
有时将其称为“ 圈复杂度 ”。 还有一些TSLint规则可以检测循环/认知复杂性,您可以在项目中使用这些规则来避免错误并检测代码气味和可维护性问题。
15)干 (15) DRY)
Do not Repeat Yourself. Make sure you do not have the same code copied into different places in the codebase. Extract the repeating code and use it in place of the repeated code.
不要重复自己。 确保没有将相同的代码复制到代码库的不同位置。 提取重复代码并使用它代替重复代码。
Why?
为什么?
Having the same code in multiple places means that if we want to make a change to the logic in that code, we have to do it in multiple places. This makes it difficult to maintain and also is prone to bugs where we could miss updating it in all occurrences. It takes longer to make changes to the logic and testing it is a lengthy process as well.
在多个位置具有相同的代码意味着,如果我们要更改该代码中的逻辑,则必须在多个位置进行更改。 这使其难以维护,并且还容易出现一些错误,在所有情况下我们都可能错过对其进行更新。 更改逻辑和测试它花费的时间也很长。
In those cases, extract the repeating code and use it instead. This means only one place to change and one thing to test. Having less duplicate code shipped to users means the application will be faster.
在这种情况下,请提取重复代码并使用它。 这意味着只有一个地方可以更改,而一件事可以测试。 发送给用户的重复代码较少,意味着应用程序将更快。
16)添加缓存机制 (16) Add caching mechanisms)
When making API calls, responses from some of them do not change often. In those cases, you can add a caching mechanism and store the value from the API. When another request to the same API is made, check if there is a value for it in the cache and if so, use it. Otherwise, make the API call and cache the result.
进行API调用时,其中一些响应不会经常更改。 在这种情况下,您可以添加缓存机制并存储API中的值。 当对同一API发出另一个请求时,请检查缓存中是否有该值,如果有,请使用它。 否则,请进行API调用并缓存结果。
If the values change but not frequently, you can introduce a cache time where you can check when it was last cached and decide whether or not to call the API.
如果值更改但不经常更改,则可以引入一个缓存时间,您可以在其中检查上次缓存的时间,并确定是否调用API。
Why?
为什么?
Having a caching mechanism means avoiding unwanted API calls. By only making the API calls when required and avoiding duplication, the speed of the application improves as we do not have to wait for the network. It also means we do not download the same information over and over again.
具有缓存机制意味着避免不必要的API调用。 通过仅在需要时进行API调用并避免重复,应用程序的速度得以提高,因为我们不必等待网络。 这也意味着我们不会一遍又一遍地下载相同的信息。
17)避免模板中的逻辑 (17) Avoid logic in templates)
If you have any sort of logic in your templates, even if it is a simple &&
clause, it is good to extract it out into its component.
如果模板中有任何逻辑,即使它是一个简单的&&
子句,也可以将其提取到其组件中。
Why?
为什么?
Having logic in the template means that it is not possible to unit test it and therefore it is more prone to bugs when changing template code.
模板中包含逻辑意味着无法对其进行单元测试,因此更改模板代码时更容易出现错误。
Before
之前
// template
<p *ngIf="role==='developer'"> Status: Developer </p>
// component
public ngOnInit (): void {
this.role = 'developer';
}
After
后
// template
<p *ngIf="showDeveloperStatus"> Status: Developer </p>
// component
public ngOnInit (): void {
this.role = 'developer';
this.showDeveloperStatus = true;
}
18)字符串应该是安全的 (18) Strings should be safe)
If you have a variable of type string that can have only a set of values, instead of declaring it as a string type, you can declare the list of possible values as the type.
如果您有一个字符串类型的变量,它只能有一组值,而不是将其声明为字符串类型,则可以将可能值的列表声明为类型。
Why?
为什么?
By declaring the type of the variable appropriately, we can avoid bugs while writing the code during compile time rather than during runtime.
通过适当地声明变量的类型,我们可以避免在编译时而不是在运行时编写代码时出现错误。
Before
之前
private myStringValue: string;
if (itShouldHaveFirstValue) {
myStringValue = 'First';
} else {
myStringValue = 'Second'
}
After
后
private myStringValue: 'First' | 'Second';
if (itShouldHaveFirstValue) {
myStringValue = 'First';
} else {
myStringValue = 'Other'
}
// This will give the below error
Type '"Other"' is not assignable to type '"First" | "Second"'
(property) AppComponent.myValue: "First" | "Second"
图片更大 (Bigger picture)
国家管理 (State Management)
Consider using @ngrx/store for maintaining the state of your application and @ngrx/effects as the side effect model for store. State changes are described by the actions and the changes are done by pure functions called reducers.
考虑使用@ ngrx / store维护应用程序的状态,并使用@ ngrx / effects作为store的副作用模型。 状态更改由动作描述,而更改由称为reduces的纯函数完成。
Why?
为什么?
@ngrx/store isolates all state related logic in one place and makes it consistent across the application. It also has memoization mechanism in place when accessing the information in the store leading to a more performant application. @ngrx/store combined with the change detection strategy of Angular leads to a faster application.
@ ngrx / store将所有与状态相关的逻辑隔离在一个地方,并使其在整个应用程序中保持一致。 当访问商店中的信息以实现更高性能的应用程序时,它也具有备忘录机制。 @ ngrx / store与Angular的更改检测策略相结合,可以加快应用程序的速度。
不变的状态 (Immutable state)
When using @ngrx/store, consider using ngrx-store-freeze to make the state immutable. ngrx-store-freeze prevents the state from being mutated by throwing an exception. This avoids accidental mutation of the state leading to unwanted consequences.
使用@ ngrx / store时 ,请考虑使用ngrx-store-freeze使状态不变。 ngrx-store-freeze通过引发异常来防止状态发生突变。 这避免了状态的意外突变导致不必要的后果。
Why?
为什么?
Mutating state in components leads to the app behaving inconsistently depending on the order components are loaded. It breaks the mental model of the redux pattern. Changes can end up overridden if the store state changes and re-emits. Separation of concerns — components are view layer, they should not know how to change state.
组件中的突变状态会导致应用程序的行为不一致,具体取决于加载组件的顺序。 它打破了redux模式的思维模式。 如果存储状态更改并重新发出,则更改最终会被覆盖。 关注点分离-组件是视图层,它们不应该知道如何更改状态。
笑话 (Jest)
Jest is Facebook’s unit testing framework for JavaScript. It makes unit testing faster by parallelising test runs across the code base. With its watch mode, only the tests related to the changes made are run, which makes the feedback loop for testing way shorter. Jest also provides code coverage of the tests and is supported on VS Code and Webstorm.
Jest是FacebookJavaScript单元测试框架。 通过跨代码库并行运行测试,可以使单元测试更快。 在其监视模式下,仅运行与所做更改相关的测试,这使得测试的反馈回路更短。 笑话 还提供了测试的代码范围,并且在VS Code和Webstorm上受支持。
You could use a preset for Jest that will do most of the heavy lifting for you when setting up Jest in your project.
您可以为Jest使用预设 ,在您的项目中设置Jest时,它将为您完成大部分繁重的工作。
业力 (Karma)
Karma is a test runner developed by AngularJS team. It requires a real browser/DOM to run the tests. It can also run on different browsers. Jest doesn’t need chrome headless/phantomjs to run the tests and it runs in pure Node.
Karma是AngularJS团队开发的测试运行程序。 它需要一个真实的浏览器/ DOM来运行测试。 它也可以在不同的浏览器上运行。 Jest不需要chrome headless / phantomjs来运行测试,它可以在纯Node中运行。
普遍 (Universal)
If you haven’t made your app a Universal app, now is a good time to do it. Angular Universal lets you run your Angular application on the server and does server-side rendering (SSR) which serves up static pre-rendered html pages. This makes the app super fast as it shows content on the screen almost instantly, without having to wait for JS bundles to load and parse, or for Angular to bootstrap.
如果您尚未将自己的应用程序设置为通用应用程序,那么现在是进行此操作的好时机。 Angular Universal允许您在服务器上运行Angular应用程序,并执行服务器端渲染(SSR),以提供静态的预渲染html页面。 这使该应用程序变得非常快,因为它几乎可以立即在屏幕上显示内容,而不必等待JS捆绑软件加载和解析,也无需等待Angular进行引导。
It is also SEO friendly, as Angular Universal generates static content and makes it easier for the web crawlers to index the application and make it searchable without executing JavaScript.
它也是SEO友好的,因为Angular Universal生成静态内容,并使Web爬网程序更容易为应用程序编制索引并使其可搜索而无需执行JavaScript。
Why?
为什么?
Universal improves the performance of your application drastically. We recently updated our application to do server side rendering and the site load time went from several seconds to tens of milliseconds!!
Universal极大地提高了应用程序的性能。 我们最近更新了我们的应用程序以进行服务器端渲染,并且站点加载时间从几秒变为数十毫秒!
It also allows your site to correctly show up in social media preview snippets. The first meaningful paint is really fast and makes content visible to the users without any unwanted delays.
它还可以使您的网站正确显示在社交媒体预览片段中。 第一个有意义的绘画确实非常快,并且使内容对用户可见,而没有任何不必要的延迟。
结论 (Conclusion)
Building applications is a constant journey, and there’s always room to improve things. This list of optimisations is a good place to start, and applying these patterns consistently will make your team happy. Your users will also love you for the nice experience with your less buggy and performant application.
构建应用程序是一个不断的过程,并且总有改进的空间。 此优化列表是一个很好的起点,并且始终如一地应用这些模式将使您的团队感到高兴。 您的用户也将爱您,因为它的bug和性能较低的应用程序可提供出色的体验。
Thank you for reading! If you enjoyed this article, please feel free to ? and help others find it. Please do not hesitate to share your thoughts in the comments section below. Follow me on Medium or Twitter for more articles. Happy coding folks!! ? ☕️
感谢您的阅读! 如果您喜欢这篇文章,请随意 ? 并帮助其他人找到它。 请不要在下面的评论部分中分享您的想法。 在Medium或Twitter上关注我以获取更多文章。 祝大家编码愉快!! ? ☕️
angular 最佳实践