Poznajemy lepiej typy tablicowe, słówko kluczowe infer oraz robimy coś mega, czyli piszemy nasz pierwszy typ rekurencyjny. Do dzieła.

Ok, zabawmy się indeksami:

const MyArray = [
    { name: "Alice", age: 15 },
    { name: "Bob", age: 23 },
    { name: "Eve", age: 38 },
  ];

type Person333 = typeof MyArray[number];
  
//   type Person333 = {
//     name: string;
//     age: number;
// }

Mam nadzieję, że to nikogo nie dziwi. To teraz przykład łatwiejszy:

const AnotherArray = [1,2,3,4];

type Type333 = typeof AnotherArray[number];
  
//type Type333 = number

Typ number to typ indeksów tablic. W obiektach jest typ keyof any, którym obecnie jest number, string albo symbol, tablice mogą mieć tylko number, także ta składnia powinna nie być trudna do ogarnięcia.

Zresztą, jedno z drugim można łączyć:

const MyArray = [
    { name: "Alice", age: 15 },
    { name: "Bob", age: 23 },
    { name: "Eve", age: 38 },
  ];

type Person333age = typeof MyArray[number]["age"];

//typePerson333age = number

Ok, to napiszmy sobie flatten:

type Flatten<T> = T extends any[] ? T[number] : T;

Chyba proste, nie? Użycie:

const MyArray = [
    { name: "Alice", age: 15 },
    { name: "Bob", age: 23 },
    { name: "Eve", age: 38 },
  ];

const AnotherArray = [1,2,3,4];

type Flatten<T> = T extends any[] ? T[number] : T;

type Person444 = Flatten<typeof MyArray>
// type Person444 = {
//     name: string;
//     age: number;
// }
type Type444 = Flatten<typeof AnotherArray>
// type Type444 = number

Ok, to teraz zapiszemy to samo, ale tym razem ze słówkiem kluczowym infer, które już nieco poznaliśmy:

const MyArray = [
    { name: "Alice", age: 15 },
    { name: "Bob", age: 23 },
    { name: "Eve", age: 38 },
  ];

const AnotherArray = [1,2,3,4];

type Flatten<T> = T extends Array<infer NestedType> ? NestedType : T;

type Person444 = Flatten<typeof MyArray>

// type Person444 = {
//     name: string;
//     age: number;
// }

type Type444 = Flatten<typeof AnotherArray>

// type Type444 = number

Ok, działa, problem jest taki, że możemy mieć bardziej zagnieżdżone typy:

 const nestedArr = [ [[1],[2],[3]], [[4],[5],[6]]];

 type nestedArrType = typeof nestedArr;
 
 //type nestedArrType = number[][][]

I co nam tu flatten zrobi? Po prostu jeden [] zabierze:

type flat = Flatten<nestedArrType>

//type flat = number[][]

Rekurencyjne typy są łatwiejsze, niż się wydają:

type FlattenNested<T> = T extends Array<infer Nested> ? FlattenNested<Nested> : T;

Jak rozumiemy, co robi infer, nie powinniśmy mieć żadnego problemu z nimi:

type FlattenNested<T> = T extends Array<infer Nested> ? FlattenNested<Nested> : T;

const nestedArr = [ [[1],[2],[3]], [[4],[5],[6]]];

type nestedArrType = typeof nestedArr;

//type nestedArrType = number[][][]

type nestedArrFlatType = FlattenNested<nestedArrType>

//type nestedArrFlatType = number

Oczywiście po internecie krążą inne przykłady rekurencyjnych typów, które wcale łatwe nie są, np. typ Strlen, który podaje jako typ zwracany konkretną liczbę, będącą tego napisu długością.

I to zapewne warto poznawać, warto próbować osiągnąć mistrzostwo w TS, aczkolwiek chyba można śmiało powiedzieć, że rekurencyjne typy w TS są zazwyczaj bardzo, bardzo łatwe.