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!