W tej lekcji będzie więcej extends keyof i typowania po indeksie. Szybka lekcja, ale konieczna, powoli budujemy dość solidne fundamenty wiedzy i umiejętności w TS. Zaczynajmy.
Ok, mam nadzieję, że to pamiętamy i nie trzeba wyjaśniać, bo dzisiaj się przyda:
type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"];
//type Age = number;
Przypomnijmy sobie generyczny extends keyof:
const getObjKeyVal = <T extends object, K extends keyof T> (obj: T, key: K) =>
{
return obj[key];
}
Pamiętamy, co z tym robiliśmy? Jak nie, to przypomnijmy:
interface Student {
name: string,
GPA: number,
classes?: number[]
}
const student : Student = {
name: "Jane",
GPA: 3.5,
classes: [100, 200]
};
console.log(getObjKeyVal(student, 'name')); //Jane
Pamiętajmy też, że typy generyczne są bardziej luźne, niż typy konkretne. Extends tam występuje i oznacza właśnie typ, który zawiera się w typie, dziedziczy z niego albo zawiera pola danego typu (+ ewentualne coś własnego).
I tam, w generycznych extends keyof występuje. Oto niegeneryczny, ścisły odpowiednik:
const getStudentKeyVal = (student: Student, key: keyof Student) =>
{
return student[key];
}
console.log(getStudentKeyVal(student, 'name')); //Jane
Konkretny typ Student, konkretny typ keyof Student. Na tym etapie to chyba już proste.
Ok, ale wracamy do typowania, bo to są operacje, to są rzeczy, które (częściowo) są kompilowane do JSa. To już mamy za sobą, teraz typy, typy, jeszcze raz typy.
Machnijmy sobie taki interfejs:
interface Employee2 {
id: number;
name: string;
age: number;
salary: number;
}
I teraz mega rzecz, generyczny utility type:
type ValueOf<T extends object, K extends keyof T> = T[K];
type EmpIdVal = ValueOf<Employee2, 'id'>;
//type EmpIdVal = number
T ma być obiektem, K kluczem tego obiektu. Zwracamy T[K] czyli… czyli dla typu T sprawdzamy jaki typ kryje się pod kluczem K i ten typ zwracamy!
I gdyby ktoś próbował wpisać zamiast 'id’ jakiś klucz, którego nie ma w Employee2 to będzie błąd. Zamieńmy sobie typ zwracany przez id z number na string i zobaczymy, jak to dynamicznie działa.
I tak – to są rzeczy, które do JSa się nie kompilują. Płynnie przeszliśmy z głupotek TSowych do czystego TSa. Ok.
To teraz typ, który zawsze dla danego obiektu zwróci typ jego id:
type ValueOfId<T extends {id: any}> = T["id"];
type EmpIdVal2 = ValueOfId<Employee2>;
//type EmpIdVal2 = number
Tu musimy zabawić się w taki literał z typem any, inaczej TS nie wie, czy T posiada klucz id. Tym niemniej, chyba aż takie trudne to nie jest i widzimy jak na dłoni, co robi.
Ok, z docsów o index types powinniśmy pamiętać taki przykład:
const myArray = [
{ name: "Alice", age: 15 },
{ name: "Bob", age: 23 },
{ name: "Eve", age: 38 },
];
type Age321 = typeof myArray[number]["age"];
Teraz pokombinujmy sobie z operatorami i typami generycznymi, aby dostać taki utility type, który zwraca nam typ pod kluczem.
Ja już pokombinowałem, oto wynik:
const myArray = [
{ name: "Alice", age: 15 },
{ name: "Bob", age: 23 },
{ name: "Eve", age: 38 },
];
type Age321 = typeof myArray[number]["age"];
type ArrKey<T extends Array<object>, K extends keyof T[number]> = T[number][K];
type nameFromArr = ArrKey<typeof myArray, 'name'>;
Przeanalizujmy to, postarajmy się zrozumieć, zróbmy sobie przerwę a ja już się zabieram za kolejne tutoriale TSa.