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…