Poznajemy interfejsy. Kolejna z lekcji „nudne początki TSa”, musimy się jakoś przez to przebić. Na pocieszenie dodam, że większość z tych tematów przekłada się 1 do 1 na takie języki jak C#.
Ok, rzecz pierwsza, czyli typ vs interfejs:
type Vehicle = {
wheels: number;
maker: string;
};
interface Vehicle {
wheels: number;
maker: string;
}
Żadnej różnicy nie ma na razie, ale gdybyśmy chcieli coś więcej, to z typu niewiele da się wycisnąć. Może to wydawać się mało oczywiste, ale interfejsy mogą też z siebie dziedziczyć:
interface Vehicle {
wheels: number;
maker?: string;
}
interface Car extends Vehicle {
power: "gas" | "electricity";
}
interface Bicycle extends Vehicle {
folding: boolean;
}
Tu mamy kilka rzeczy:
- marker może być albo string, albo undefined (inaczej string | undefined)
- Interfejsy dziedziczą z siebie
- Car ma bardzo konkretny typ, nie string tylko konkretne stringi przyjmuje
Tu mamy przykład jak się implementuje interfejs w klasie:
interface IEmployee {
empCode: number;
name: string;
getSalary:(empCode: number) => number;
}
class Employee implements IEmployee {
empCode: number;
name: string;
constructor(code: number, name: string) {
this.empCode = code;
this.name = name;
}
getSalary(empCode:number):number {
return 20000;
}
}
let emp = new Employee(1, "Steve");
Declaration merging to też ciekawa rzecz:
interface User{
id: number;
name: string;
job: string;
salary: string;
}
interface User {
gender: string;
isMarried: boolean;
}
I teraz chodzi tu o to, że nasz user ma mieć wszystkie pola z jednego i drugiego tak samo nazwanego interfejsu. Gdybyśmy chcieli mieć coś „do wyboru” to byśmy zastosowali dziedziczenie jak w poprzednim przykładzie.
Declaration merging jest po to, aby jakoś to zorganizować tak, aby nam było wygodnie, to nie ma związku z logiką. Jak chcesz logiki, to zrób interfejs z głównymi properties, zrób dziedziczące interfejsy, tam sobie różne rzeczy ustal i masz dwie opcje.
Declaration merging to nie dwie opcje tylko taki myk na podzielenie tego kodu. Tu mamy ciekawy przykład:
interface Document {
createElement(tagName: any): Element;
}
interface Document {
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
}
interface Document {
createElement(tagName: string): HTMLElement;
createElement(tagName: "canvas"): HTMLCanvasElement;
}
I teraz efekt tego taki, że dostajemy taki interfejs:
interface Document {
createElement(tagName: "canvas"): HTMLCanvasElement;
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
createElement(tagName: string): HTMLElement;
createElement(tagName: any): Element;
}
I od razu muszę wspomnieć – nie ma w TS takiego przeładowywania jak w innych językach. Że jedna funkcja o jednej nazwie, ale różnej ilości argumentów, różnie nazwanych i tak dalej.
W TS możesz przeładowywać tylko w jeden sposób, to jest ta sama nazwa, ta sama ilość argumentów tak samo nazwanych, tylko typ inny. Zobaczmy taki kodzik po kompilacji:
function add(a, b) {
return a + b;
}
To oznacza, że tych funkcji add możemy mieć ile chcemy, ale każda z nich będzie mieć 2 argumenty, a i b, tylko typy mogą być inne.
Ok, jeszcze jedna rzecz a mianowicie to (nie wiem jak to nazwać, indeksery?):
interface ArrLikeObj {
[index: number] : number;
length: number;
}
let obj : ArrLikeObj = {
0: 1,
1: 2,
length: 2
}
Czyli może być artybut length, który zwraca number, a wszystko inne, to mają być klucze tylko numeryczne zwracające wartość tylko numeryczną.
To może generyczny odpowiednik teraz:
interface ArrLikeObjGen<T> {
[index: number] : T;
length: number;
}
let obj2 : ArrLikeObjGen<string> = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
Ok więcej o interfejsach w następnych lekcjach. Po co są interfejsy to już mówiliśmy i widzieliśmy w PHP w projekcie framework MVC. To wszystko jest dla pisania lepszego kodu, dla errorów podczas kompilacji, na runtime to nie ma wpływu.