Wchodzimy już na grubo w TypeScripta. Pierwszy mapped type napisaliśmy w lekcji poprzedniej, pora iść za ciosem. Do dzieła!

Ok, zróbmy sobie taki obiekcik a w zasadzie interfejs:

interface Employee2 {
    id: number;
    name: string;
    age: number;
    salary: number;
}

Teraz chcemy typ, który będzie mieć klucze tego obiektu. Iterować i mapować raczej już potrafimy:

type EmpStringified = {
    [P in keyof Employee2]: string;
}

// type EmpStringified = {
//     id: string;
//     name: string;
//     age: string;
//     salary: string;
// }

Ok, teraz dodajmy jeszcze, żeby generyczny był:

type Stringify<T> = {
    [P in keyof T]: string;
}

type EmployeeAsString = Stringify<Employee2>;

// type EmployeeAsString = {
//     id: string;
//     name: string;
//     age: string;
//     salary: string;
// }

Tak samo możemy zrobić utility type OptionFlags:

type OptionsFlags<T> = {
    [P in keyof T]: boolean;
   };

type EmployeeOptions = OptionsFlags<Employee2>;

// type EmployeeOptions = {
//     id: boolean;
//     name: boolean;
//     age: boolean;
//     salary: boolean;
// }

Możemy też w wartości/typie zwracanym odnieść się do typu klucza głównego typu. Znaczy T[P] zrobić, na przykładzie lepiej to widać:

type ValidatorOptions<T> = {
    [P in keyof T]: {
        type: T[P];
        rules: string[];
    }
}

type EmployeeValidatorOptions = ValidatorOptions<Employee2>;

// type EmployeeValidatorOptions = {
//     id: {
//         type: number;
//         rules: string[];
//     };
//     name: {
//         type: string;
//         rules: string[];
//     };
//     age: {
//         type: number;
//         rules: string[];
//     };
//     salary: {
//         type: number;
//         rules: string[];
//     };
// }

Dla każdego property w T robimy jako wartość obiekt, który w type ma T[P] (typ zwracany przez ten klucz) oraz tablicę napisów o nazwie rules.

Przypomnijmy sobie raz jeszcze typ MyRecord, czyli odtworzenie typescriptowego utility type record:

type Streams = 'salary' | 'bonus' | 'sidehustle'

type MyRecord<K extends string|number|symbol, T> = {
    [P in K]: T;
};
type Incomes = MyRecord<Streams, number>

// type Incomes = {
//     salary: number;
//     bonus: number;
//     sidehustle: number;
// }

const monthlyIncomes: Incomes = {
    salary: 500,
    bonus: 100,
    sidehustle: 250
}

for (const revenue in monthlyIncomes) {
    console.log(monthlyIncomes[revenue as keyof Incomes])
}

W poprzedniej lekcji to robiliśmy. Ok, rzućmy raz jeszcze okiem na interfejs Employee2:

interface Employee2 {
    id: number;
    name: string;
    age: number;
    salary: number;
}

Teraz napiszemy sobie utility ValueOf oraz ValueOfId. Dodam, że jeszcze infera nie znamy, z inferem jeszcze lepiej to sobie zrobimy, ale nie uprzedzajmy faktów:

type ValueOf<T extends object, K extends keyof T> = T[K];

type EmpIdVal = ValueOf<Employee2, 'id'>;

//type EmpIdVal = number

Jak widać ten typ z każdego typu wyciągnie wartość jego klucza. I oczywiście musimy podać typ, który jest jakimś obiektem i klucz, który jest jego typem a w zamian dostaniemy typ tego klucza.

Teraz nieco inne utility, które zawsze poda nam typ dla pola id:

type ValueOfId<T extends {id: any}> = T["id"];

type EmpIdVal2 = ValueOfId<Employee2>;

//type EmpIdVal2 = number

Ten wymaga od nas, aby mu zawsze podać tylko jedną rzecz, byle jaki obiekt, ale ten obiekt musi mieć id. W zamian on nam poda typ tego id.

Ok, jeszcze jeden mały mapped type, czyli nullable:

type Nullable<T> = {
    [K in keyof T]: T[K] | null
  };

type NullableEmp = Nullable<Employee2>;

// type NullableEmp = {
//     id: number | null;
//     name: string | null;
//     age: number | null;
//     salary: number | null;
// }

Mam nadzieję, że na tym poziomie to już się samo komentuje.

Ok, więcej TSa niedługo!