Kontynuujemy poznawanie mapped types, czyli bardziej zaawansowany TypeScript się kłania. Zaczynamy.
Ok, weźmy ten interfejs:
interface Employee2 {
id: number;
name: string;
age: number;
salary: number;
}
Teraz pomyślmy co by wyszło z takiego kodu:
type Getter<T> = {
[P in keyof T]: () => T[P]
};
type EmpGetters = Getter<Employee2>;
Możemy to sprawdzić w edytorze, jak nie to śpieszę z pomocą:
type Getter<T> = {
[P in keyof T]: () => T[P]
};
type EmpGetters = Getter<Employee2>;
// type EmpGetters = {
// id: () => number;
// name: () => string;
// age: () => number;
// salary: () => number;
// }
Teraz warto będzie poznać dość dziwny utility type, jakim jest Capitalize. Dziwny bo tam się przekazuje P & string i sam nie wiem czemu:
type CapitalizeKeys<T> = {
[P in keyof T as `${Capitalize<string & P>}`]: T[P];
};
type EmplCapitalized = CapitalizeKeys<Employee2>;
Ok, co z tego wyjdzie? Pomyślmy, sprawdźmy, koniec końców takie coś dostaniemy:
type CapitalizeKeys<T> = {
[P in keyof T as `${Capitalize<string & P>}`]: T[P];
};
type EmplCapitalized = CapitalizeKeys<Employee2>;
// type EmplCapitalized = {
// Id: number;
// Name: string;
// Age: number;
// Salary: number;
// }
Ok, pal licho to Capitalize, nie wiemy, dlaczego tak działa, ale wiemy jak działa. To połowa sukcesu, napiszmy zatem sobie teraz getter, ale ma się trzymać konwencji getProp:
type Getter<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P]
};
I jak to wygląda? W ten sposób:
type Getter<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P]
};
type EmpGetters = Getter<Employee2>;
// type EmpGetters = {
// getId: () => number;
// getName: () => string;
// getAge: () => number;
// getSalary: () => number;
// }
Ok, to settera sobie pyknijmy, załóżmy, że ma zwracać boolean:
type Setter<T> = {
[P in keyof T as `set${Capitalize<string & P>}`]: (newVal: T[P]) => boolean
};
Pomyślmy, czy wszystko nam tu pasuje, następnie sprawdzamy, co za typ dostaliśmy:
type Setter<T> = {
[P in keyof T as `set${Capitalize<string & P>}`]: (newVal: T[P]) => boolean
};
type EmpSetters = Setter<Employee2>;
// type EmpSetters = {
// setId: (newVal: number) => boolean;
// setName: (newVal: string) => boolean;
// setAge: (newVal: number) => boolean;
// setSalary: (newVal: number) => boolean;
// }
Ok, fajnie, a teraz chcemy aby było coś takiego jak withGetters. Czyli ten obiekt plus gettery. Cóż, trzeba trochę się nad tym pozastanawiać:
type WithGetters<T> = {
[P in keyof T]: T[P];
} & {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P]
};
I dostajemy taki typ:
type EmpWithGetters = WithGetters<Employee2>
// type EmpWithGetters = {
// id: number;
// name: string;
// age: number;
// salary: number;
// } & {
// getId: () => number;
// getName: () => string;
// getAge: () => number;
// getSalary: () => number;
// }
Ok, to teraz zróbmy sobie with getters and setters:
type WithGetterSetters<T> = {
[P in keyof T]: T[P];
} & {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P]
} & {
[P in keyof T as `set${Capitalize<string & P>}`]: (newVal: T[P]) => boolean
};
I wynik tutaj nie powinien nikogo dziwić:
type EmpGettersSetters = WithGetterSetters<Employee2>
// type EmpGettersSetters = {
// id: number;
// name: string;
// age: number;
// salary: number;
// } & {
// getId: () => number;
// getName: () => string;
// getAge: () => number;
// getSalary: () => number;
// } & {
// setId: (newVal: number) => boolean;
// setName: (newVal: string) => boolean;
// setAge: (newVal: number) => boolean;
// setSalary: (newVal: number) => boolean;
// }
Więcej TSa już niedługo…