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?
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
}
}