Types in TypeScript

Andrzej Kopeć

No dobra, to jak je w ogóle zadeklarować?

Ano tak:

let variable: SomeType;
const constant : SomeClass = new SomeClass()

function (arg: ArgType) : ReturnType {}

Typy podstawowe

  • Boolean/boolean
  • Number/number
  • String/string

Ej, ej, a te z []?

let variable1 : Type[]; //Tablica obiektów typu "type"
let variable2: Array<Type>; // jak wyżej
let variable3 : [Type1, Type2]; //Krotka (tuple)

To jak mam opisać np. produkt?

class Product {
    constructor (public name: string, public price: number) {}
}

type Product = {
    name: string;
    price: number;
}

interface Product {
    name: string;
    price: number;
}

A jak zrobić opcjonalne pole z opisem?

Pole opcjonalne

interface Product {
    ...
    description?: string;
}

interface Product {
    ...
    description: string | null;
}

interface Product {
    ...
    description: string | undefined;
}

No ok, ale chcę dodać paginację, nie będę robił przecież paginatora dla każdej encji!

Nie, zdefiniujesz typ generyczny, o tak:

class Paginator<T> {
    constructor (private allElements: T[]) {}

    getPage (nr: number): T[] {}
}

A jak mam niby zdefiniować, że w danej zmiennej może być cokolwiek? Np. w jakimś kliencie Http?

interface HttpClient {
    post (url: string, data: any): Promise<any>
    put (url: string, data: object): Promise<any>
    patch (url: string, data: Object): Promise<any>
}

Ale takie jQuery na przykład jeszcze ma callbacka, ja też chcę!

interface HttpClient {
    get (url: string, callback: (data: any) => void): void;
    delete (url: string, callback: (data: any) => void): void;
}

A czy mogę jakoś zrobić, by moja wewnętrzna metoda "makeRequest" mogła przyjmować tylko określone wartości?

Tak, z union types albo enumami

type HttpMethod = "get" | "post" | "delete";

makeRequest("get");

enum HttpMethod {Get, Post, Delete};

makeRequest(HttpMethod.Get);

Ale to "any" praktycznie wyłącza mi tam sprawdzanie typów!

interface HttpClient {
    get<T>(url: string): Promise<T>;
}

function makeGet<T> (url: string): Promise<T> {};

const makeGet = <T>(url: string): Promise<T> => {}

No ok, a co jak się spodziewam, że mam jakiś konkretny typ a deklaracje mówią inaczej?

Na to są 2 rozwiązania:

  • Rzutowanie
  • Type guard

Rzutowanie

let variable: string|number;

const string1 = variable as string;
const string2 = <string>variable;

Type guard

function isProduct (product: any) product is Product {
    return product.name && product.price;
}

let productOrCategory: Product | Category;

if (isProduct(productOrCategory)) {
    //Product
}

No działa, ale jak mam wyświetlić listę wyników wyszukiwania, na której są i produkty i kategorie?

1. opcja - union types

interface Category {
    name: string;
    image: string;
    products: Product[];
}

interface Product {
    name: string;
    image: string;
    price: Money;
}

type SearchResultItem = Category | Product;

let item : SearchResultItem;
item.name; //OK;
item.image; //OK;
item.price //Error

2. opcja - bazowy interfejs

interface ShopItem {
    name: string;
    image: string;
}

interface Category extends ShopItem {
    products: Product[];
}

interface Product extends ShopItem {
    price: Money;
}

type SearchResultItem = ShopItem;

let item : SearchResultItem;
item.name; //OK;
item.image; //OK;
item.price //Error

No, prawie, działa, bo backend do wyniku wyszukiwania dodaje link do szczegółów...

type WithDetailsLink = {detailsUrl: string;};
type ProductOrCategory = Product | Category;

type SearchResultsItem = ProductOrCategory & WithDetailsLink;

let item : SearchResultsItem;
item.detailsUrl; // OK

Swoją drogą, w JS Intersection Types są dość popularne w różnych bibliotekach:

function assign<T, U> (t: T, u: U): T&U {}

Podobnie jak te kilka wytrychów:

type Flags = { [name: string]: boolean};
type Flags = Record<string, boolean>

function get<T, U extends keyof T>(obj: T, key: U): T[U] {}

type Partial<T> = { //wbudowane w TypeScript
    [P in keyof T]?: T[P];
}

PS. Dla użytkowników reduxa

const ADD_PRODUCT = 'AddProduct';
interface AddProductAction {
    type: typeof ADD_PRODUCT;
    payload: Product;
}

const REMOVE_PRODUCT = 'RemoveProduct';
interface RemoveProductAction {
    type: typeof REMOVE_PRODUCT;
    payload: string;
}

type ProductAction = AddProductAction | RemoveProductAction;

function handleProduct(action: ProductAction) {
    switch (action.type) {
        case ADD_PRODUCT:
            //action.payload is Product
        case REMOVE_PRODUCT:
            //action.payload is string
    }
}

Questions time!

andrzej.kopec@outlook.com