Kilka słów o operatorze keyof w TS. Szybka lekcja. Zaczynajmy.

Przypomnijmy sobie tę funkcję:

const getObjKeyVal = <T extends object, K extends keyof T> (obj: T, key: K) => 
    {
        return obj[key];
    }

Ok, użyjemy sobie przykładu z internetu aby wypróbować tę funkcję, którą napisaliśmy ileś lekcji temu:

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

No fajnie, fajnie, działa. Ok, ale nie chcemy funkcji generycznej teraz – chcemy taką, która akceptuje studenta. Z drugiej strony ma być constraint na drugi argument jako klucz.

const getStudentKeyVal = (student: Student, key: keyof Student) => 
    {
        return student[key];
    }

console.log(getStudentKeyVal(student, 'name')); //Jane

Ok, wszystko fajnie, natomiast co ta funkcja zwraca? Rzućmy okiem na interfejs:

interface Student {
    name: string,
    GPA: number,
    classes?: number[]
}

Interesuje nas tylko to, co po prawej oraz fakt, że po lewej mamy operator ? w jednym miejscu. Z tego wnioskujemy, że return type to:

string | number | number[] | undefined

I tak nam podpowiada edytor. Może nie jestem najlepszy w tłumaczeniu, ale zbędne nazewnictwo tylko komplikuje sytuację. To są rzeczy, które może zwrócić nam Student odwołując się do niego po kluczu – i tyle.

Ok, ale przykład z internetu, którym trochę się pobawiliśmy, był tam w innym kontekście. Tutaj podaję, o co chodzi:

for (const key in student) {
    console.log(`${key}: ${student[key as keyof Student]}`)
};

Object.keys(student).map(key => {
    console.log(student[key as keyof Student]);
});

Tylko się nie przeraźmy. Ja mam tsconfig tak ustawiony, że mi to nie jest potrzebne (dopiero przestawiam się na takie ścisłe, ścisłe typowanie). Ale ok, niech to nam rozwali głowę, zwłaszcza, że można też tak:

for (const key in student) {
    console.log(`${key}: ${student[key as keyof typeof student]}`)
};

Object.keys(student).map(key => {
    console.log(student[key as keyof typeof student]);
});

Tylko dzięki luźnym ustawieniom tscofig ja tego nie muszę robić (ale mogę). Natomiast trochę kombinowania mi to zajęło, aby zrozumieć o co chodzi. W internecie wbrew pozorom podają tylko przykłady (mądre lub głupie, działające lub nie), ale nikt niczego nie tłumaczy w przystępny sposób. Ja się postaram najlepiej jak umiem.

Więc tak, zróbmy sobie taki typ:

interface Student {
    name: string,
    GPA: number,
    classes?: number[]
}

type StudentKey = keyof Student;

let key1: StudentKey = 'name'; //ok
let key2: StudentKey = 'GPA'; //ok
let key3: StudentKey = 'classes'; //ok
let key4: StudentKey = 'fullName'; //error

Chyba widać, o co chodzi. Może być string, pod warunkiem, że jest on kluczem w interfejsie Student. A keyof typeof?

Keyof typeof dotyczy obiektów określonego interfejsu:

interface Student {
    name: string,
    GPA: number,
    classes?: number[]
}

const student : Student = {
    name: "Jane",
    GPA: 3.5,
    classes: [100, 200]
};

type StudentKey = keyof typeof student;

Działanie to samo. Ok, tutaj edytor na nie podpowiada, ale mam taki przykład, gdzie dobrze podpowie:

const user = {
    name: 'John',
    age: 32
  };

type UserKeys = keyof typeof user; // 'name' | 'age'

Więcej o keyof niedługo, natomiast upewnijmy się, że to dobrze rozumiemy.