30 trików dot. Angulara oraz TypeScriptu, które usprawnią twoją aplikację
Sebastian Superczyński i Przemysław Pietrzak z Espeo postanowili zebrać 30 tricków, z których korzystali przez lata pracy w software housie i przedstawić je w poniższym artykule. Znajdziecie w nim porady dotyczące tego, by używać get i set w Input() zamiast ngOnChanges oraz by deklarować typy w tuple.
Sebastian Superczynski. Pracuje z Angularem od samego początku jego utworzenia. Pomagał w tworzeniu 20 komercyjnych projektów, w tym dla największego banku w Kanadzie. Poza frontendem pracuje również przy projektach związanych z Machine Learningiem.
Przemysław Pietrzak. Senior software developer w Espeo. Na co dzień programuje w JavaScripcie i Node.js’ie, a po nocach w Elmie i Reasonie. Fan programowania funkcyjnego i starych filmów. Twórca bibliotek: rembrandt i pyMonet. Bywa złośliwy.
Spis treści
Angular
1. Zdefiniuj parametry formularza przed definicją Komponentu
export const FORM_PARAMS = { status: 'status', classification: 'classification', note: 'note' }; @Component({ selector: 'app-preview', templateUrl: './preview.component.html', styleUrls: ['./preview.component.scss'] }) this.form = this.formBuilder.group({ [FORM_PARAMS.status]: [false], [FORM_PARAMS.classification]: [null], [FORM_PARAMS.note]: [null, Validators.required] }); this.form.patchValue({ [FORM_PARAMS.status]: 'status' });
2. Używaj addClass i removeClass, gdzie to tylko możliwe. Pozwoli to uniknąć wielu zbędnych ticków w change detection.
// SLOWER <div [ngClass]="isRowCollapsed(rowIndex) ? 'arrowDown' : 'arrowRight'" (click)="collapseRow(rowIndex)"> </div> collapseRow(rowIndex: number) { this.setRowCollapsed(rowIndex); } // FASTER <div (click)="collapseRow($event, rowIndex)" </div> collapseRow(event, rowIndex: number) { this.setRowCollapsed(rowIndex); this.render.removeClass(event.target, 'arrowDown'); this.render.addClass(event.target, 'arrowRight'); }
3. Używaj get i set w Input() zamiast ngOnChanges. Kiedy masz wiele ifów w ngOnChanges, każdy “if” musi być osobno sprawdzany. Przy użyciu get akcja wykonywana jest od razu na właściwej zmiennej.
// SLOWER when many ifs ngOnChanges(changes: SimpleChanges) { const data: SimpleChange = changes.data; if (data && data.currentValue) { this.data = [...data.currentValue]; } if (configuration && configuration.currentValue) { this.config = configuration.currentValue; } } // FASTER public _config: Config; @Input('configuration') set configuration(value: Config) { this._config = value; } get configuration(): Config { return this._config; } _data: Data; @Input('data') set data(value: Data) { this._data = [...value]; } get data(): any[] { return this._data; }
4. Używaj “pipe” kiedy korzystasz z renderowania kodu html w ngFor.
// Slower <td *ngFor="let column of columns"> {{ render(row, column) }} </td> render(row: any, column: string) { return YourService.render(row, column); } // Faster <td *ngFor="let column of columns"> {{ row | render:column }} </td> @Pipe({ name: 'render', }) export class RenderPipe implements PipeTransform { transform(row: any, column: string) { return YourService.render(row, column); } }
5. Dodaj baseUrl i path do compilerOptions, aby uniknąć zbędnych, niejasnych i nieczytelnych importów plików ts.
{ "compilerOptions": { "baseUrl": "src", "paths": { "@core/*": ["app/*"], "@assets/*": ["assets/*"] } } } import { Recruitment } from '@core/domain/recruitment'; instead import { Recruitment } from '../../../domain/recruitment';
6. Dodaj stylePreprocessorOptions do angular.json, aby uniknąć niejasnych i nieczytelnych importów plików css.
"projects": { "project-frontend": { "root": "", "sourceRoot": "src", "projectType": "app", "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "stylePreprocessorOptions": { // <--- add this "includePaths": [ "./src/assets/style/scss" ] } } } } } @import "variables"; instead @import "../../assets/style/scss/variables";
7. Uruchamiaj komendę npm outdated
i npm-check raz w miesiącu. Ułatwi Ci to aktualizację bibliotek. O wiele łatwiej jest zaktualizować Angulara 5 do 6 niż 4 do 6.
8. Uruchamiaj npm audit raz w miesiącu. Pozwoli Ci to szybko wprowadzić poprawki bezpieczeństwa w bibliotekach które tego wymagają.
if [[ $(npm audit | grep Critical -B3 -A10) != '' ]]; then exit 1; fi"
9. Podczas walidowania pól formularza używaj parent zamiast this.form, który może być jeszcze nie zainicjalizowany, kiedy wykonywana jest walidacja.
// Correct static validateEndDate(fc: FormControl) { const startDate = fc.parent.get(FORM_PARAMS.startDate); if (startDate.value) { const diff = fc.value - startDate.value; return (diff < 86400) ? { endDateInvalid: true } : null; } return null; } // Incorrect static validateEndDate(fc: FormControl) { const startDate = this.form.get(FORM_PARAMS.startDate); if (startDate.value) { const diff = fc.value - startDate.value; return (diff < 86400) ? { endDateInvalid: true } : null; } return null; }
10. Staraj się wydzielić nazwy routingu do osobnych zmiennych. Pozwoli to uniknąć przypadkowych literówek.
export class ROUTE { public static readonly LOGIN = '/login'; public static readonly RECRUITMENTS = '/recruitments'; public static readonly RECRUITMENT_EDIT = '/recruitments/:id'; } goToRecruitment($event, id: number) { $event.preventDefault(); this.router.navigate([ROUTE.RECRUITMENT_EDIT.replace(':id', id)]); }
11. Zainstaluj webpack-bundle-analyzer. Pozwoli Ci to łatwo odnaleźć, szybko rosnące moduły. W naszym przypadku w jednym pliku zamiast zaimportować variable.scss dołączyliśmy plik main.scss. Rozmiar naszego bundla zwiększył się dwukrotnie!
"scripts": { "bundle-report": "ng build --prod --stats-json && webpack-bundle-analyzer dist/stats.json" },
12. Używaj zakładki Performance w przeglądarce. Renderowanie z prędkością 17ms oznacza, że używasz 60fps. Akcje poniżej 60fps i większe niż 30fps są prawidłowe. Każdy wskaźnik poniżej 30fps sprawia, że użytkownik dostrzega zacinającą się aplikację.
13. Używaj dodatku do przeglądarki Augury.
14. Używaj Prettier jako narzędzia do formatowania kodu.
Przykładowa konfiguracja:
// .prettierrc { "printWidth": 140, "parser": "typescript", "tabWidth": 2, "singleQuote": true, "trailingComma": "all" }
Użyj tego dodatku, aby zapobiec konfliktom pomiędzy tslintem i prettierem:
https://www.npmjs.com/package/tslint-config-prettier.
Typescript
15. Za pomocą słowa kluczowego declare — pozwala ono utworzyć typ który nie jest zdefiniowany domyślnie.
http://blogs.microsoft.co.il/gilf/2013/07/22/quick-tip-typescript-declare-keyword
16. Żeby zadeklarować słownik użyj takiej składni arg: { [key: string]: number }.
Każda wartość tego obiektu będzie zadeklarowana jako number.
// GOOD const fn = (arg: { [key: string]: number }) => { const val = arg.key1 + arg.key2 + arg.key3; // number const val1 = arg.totallyRandomKey; // number const val2 = arg['wpłynąłem na suchego przestwór oceanu']; // number };
17. Za pomocą operatora ampersand możesz uzyskać:
const fn = (arg: { key: string } & { key1: number }) => 42; fn({ key: '42' }); // ERROR fn({ key1: 42 }); // ERROR fn({ key: '42', key1: 42 }); // GOOD
18. Deklaruj typy w krotkach (tuples).
let tuple: [string, number]; tuple = ["hello", 10]; // OK tuple = [10, "hello"]; // Error let str = tuple[0]; // string let num = tuple[1]; // number
19. Używaj podkreślenia, które ułatwi Ci czytać duże liczby:
let bigNumber = 123_456_678;
let bigNumber = 123456678;
20. Aby zapobiec stworzenia instancji danej klasy używaj słowa kluczowego ‘abstract’.
abstract class AbstractClass { method() { return 42 } } class Class extends AbstractClass { method1() { return 42; } } const instance = new AbstractClass(); // Cannot create an instance of an abstract class. const instance1 = new Class();
TSlint
21. Unikaj typów any. Dodaj no-unsafe-any: {“severity”: “warning”} do tslinta — otrzymasz ostrzeżenie, kiedy użyjesz tego typu w kodzie. Aby zapobiec zwiększaniu się ilości unsafe-any dodaj ten skrypt bashowy do Twojej konfiguracji CI.
if [ $(npm run lint | grep WARNING | wc -l) -gt 100 ]; then exit 1; fi
22. Dodaj no-string-literal do tsconfig — blokuje on dostęp do klucza obiektu poprzez obj[‘key’]. Tylko deklaracja obj.key jest możliwa.
// WRONG obj['key'] // GOOD obj.key
23. Dodaj typ przy deklaracji obiektu:
„no-inferrable-types”: [
true,
„ignore-params”
],
@Output() onChange = new EventEmitter(); // Explicit type parameter needs to be provided to the constructor @Output() onChange = new EventEmitter<number>(); // OK @Output() onChange = new EventEmitter<any>(); // also OK
24. Unikaj złożoności kodu:
Dodaj „parameters-max-number„: [true, 10] do tsconfig — ustawi to limit 10, jako maxymalną ilość parametrów które może przyjąć funkcja.
„cognitive-complexity„: [true, 10] — zablokowane jest dodanie więcej niż 10 “if/else/switch” w jednej funkcji.
„no-big-function„: [true, 100] — ustawi maxymalną ilość linii w kodzie na 100. Staraj się z czasem zmniejszać tę liczbę, idealnie gdyby miała wartość około 20.
25. Staraj się utrzymać czysty kod:
Dodaj no-commented-out-code do tsconfig — reguła ta nie pozwala na trzymanie w kodzie zakomentowanych linii.
// WRONG // export class ROUTE { // public static readonly LOGIN = '/login'; // public static readonly RECRUITMENTS = '/recruitments'; // public static readonly RECRUITMENT_EDIT = '/recruitments/:id'; // }
„no-duplicate-string”: [true, 5] —linter pokaże błąd kompilacji kiedy znajdzie w jednym pliku to ten sam string 5 razy.
26. Dodaj “noImpicitAny”: true to tsconfig — otrzymasz błąd kompilacji kiedy nie można odczytać typu argumentu funkcji.
// WRONG const fn = (arg) => arg; // GOOD const fn1 = (arg: any) => arg; // GOOD const fn2 = (arg: number) => arg;
27. Dodaj “noImplicitReturns”: true do tsconfig — otrzymasz błąd kompilacji kiedy będziesz próbował zwrócić różne typy w jednej funkcji w poszczególnych “ifach”.
const fn = () => { if (true) { return; // ERROR: Not all code paths return a value. } if (false) { return 42; } }
28. Dodaj “strictFunctionTypes”: true do tsconfig — otrzymasz błąd kompilacji kiedy dodany jest zły typ parametru do funkcji.
const fn = (arg: number, cb: (string) => string) => { return 42 } fn(42, a => a++); // Argument of type '(a: any) => number' is not assignable to parameter of type '(string: any) => string'. Type 'number' is not assignable to type 'string'.
29. Dodaj “noUnusedParameters”: true do tsconfig — otrzymasz błąd kompilacji kiedy we funkcji masz nieużywane jej parametry. Argumenty z podkreśleniem są dozwolone.
30. Dodaj “noUnusedLocals”: true do tsconfig — otrzymasz błąd kompilacji kiedy masz w kodzie nieużywane importy lub zmienne.
Zdjęcie główne artykułu pochodzi z pexels.com. Artykuł został przetłumaczony z bloga espeo.eu.