Co nowego w Typescript 3.8

20 lutego 2020 roku pojawił się Typescript w wersji 3.8. Jak zawsze dostaliśmy kilka ciekawych usprawnień, oto niektóre z nich:

  • wymuszone importowanie typów
  • prywatne pola z EcmaScript 2019
  • możliwość exportowania całego modułu w jednej przestrzeni nazw
  • await w głównym kontekscie uruchomieniowym

Importowanie typów

Aby umożliwić odwoływanie się do typów, Typescript wykorzystuje składnię importowania znanej z ES.

// module.ts export interface IParams { } export function foo(params: IParams): void { } // index.ts import {foo, IParams} from './module.ts'; const params: IParams = {}; foo(params);

Powyższy kod wykorzystuje słowo kluczowe import do załadowania zarówno funkcji foo i interfejsu IParams. Jeśli chcemy aby wynikowy kod Javascript zadziałał, transpiler musi usunąć interfejs IParam. Użyje do tego mechanizmu zwanego „przerwaniem importu” (eng. import elision).

Kod wyjściowy:

// module.ts export function foo(params): void { } // index.ts import {foo} from './module.ts'; const params = {}; foo(params);

Zauważ że ślad zaginął po interfejsie IParams – taki mechanizm może być problematyczny bo nie zawsze jest w stanie stwierdzić czy importujemy typ czy wartość.

W nowej wersji wprowadzono konstrukcję import type, mówiącą transpilatorowi że ładowane elementy są tylko deklaracjami i mogą być bezpiecznie usunięte. Podobnie działa export type, dostarcza mechanizmu exportowania elementów tylko do użycia jako typy.

Dodatkowo dla transpilatora została wprowadzona nowa flaga importsNotUsedAsValues która definiuje co zrobić z importami które nie zostały wykorzystane w kodzie.

  • remove – dotychczasowe zachowanie, jest ustawione jako domyślne aby kontynułować kompatybilność wstecz.
  • preserve – zachowuje wszystkie importy
  • error – zachowuje wszystkie importy (tak jak w przypadku „preserve”), jednak transpiler zgłosi błąd w przypadku gdy załadujesz moduł przy pomocy standardowego importu a użyjesz jako typu.

Pola prywatne z ES2019

EcmaScript2019 wprowadza do klas natywne pola prywatne. Nowa konstrukcja różni się jednak od tego co mamy dotychczas w Typescript.

Zamiast słowa kluczowego „private” dodajemy znak „#” na początku nazwy pola.

class Article { #title: string; // definiujemy pole prywatne constructor(title: string) { this.#title = title; } getTitle(): string { return this.#title; // do pobrania wartosci mozna uzyc metody statycznej } static getTitleFromArticle(art: Article): string { return art.#title; } } const art = new Article('Hello world!'); // do konstruktora podajemy wartość pola "title" art.#title // nie da się odwołać do pola #title // ~ // Property '#title' is not accessible outside class 'Article' // because it has a private identifier.

Pierwsze pytanie jakie się teraz nasuwa: czym różni się # od modyfikatora private? Modyfikator „private” działa tylko na etapie transpilacji a w kodzie wynikowym pole jest już upublicznione, co oznacza że w trakcie działania aplikacji dostęp jest możliwy.

W przypadku pola prywatnego #, jakiekolwiek odwołanie się do niego w trakcie działania skryptu powoduje błąd TypeError – to spora różnica.

const art1 = new Article('my title'); const art2 = { title: 'other title' }; console.log(art1.equals(art2)); // TypeError: attempted to get private field on non-instance

Kolejna różnica to że pola prywatnego nie można zadeklarować w konstruktorze, aczkolwiek nie ździwiłbym się gdyby twórcy TS to umożliwili w przyszłych wersjach.

class Article { constructor(#title: string) { // Error: Private identifiers cannot be used as parameters: } }

Private czy #?

Teraz najważniejsze pytanie: który typ pól prywatnych użyć?

Jak wcześniej wspomniałem private działa tylko na etapie transpilacji a # działa podczas uruchomienia. Można by uznać że modyfikator dostępu private daje lekką prywatnośc (soft private) a # mocną prywatność (hard private) – tak, wiem że w PL brzmi to dziwnie.

Kolejną kwestią po stronie nowych pól prywatnych jest fakt że kiedyś będą natwnie wspierane przez przeglądarki. Jednak zanim to nastąpi muszą być wspierane przez polyfile.

Modyfikator private ma tą zaletę że nie potrzebuje polyfila, co na dzień dzisiejszy oznacza że jest znacznie wydajniejszy.

export *

Ciekawy dodatek do importów. Za jego pomocą zaimportujemy wszystkie elementy do jednego literału.

Przykład:

// utils.ts export const fn1 = () => {}; export const fn2 = () => {}; // index.ts import * as utils from 'utils.ts'; utils.fn1(); utils.fn2();

Await w głównym kontekscie uruchomieniowym

Jak dotąd aby użyć await w głównym kontekscie uruchomieniowym należało opakować dany fragment kodu funkcja asyncroniczną, np. tak:

(async function () { await fetch('http://pojnhub.com'); })();

W TS 3.8 już nie trzeba tego robić:

await fetch('http://pojnhub.com');

Zdjęcie: Joshua Hoehne

Dodaj komentarz

Twój adres email nie zostanie opublikowany.