Я бы, вероятно, написал это так:
type Routes = readonly { path: string, children?: Routes }[];
type MappedRoutes<T extends Routes> = {
[K in T[number]["path"]]: (Extract<T[number], { path: K }>['children'] extends infer R ?
R extends Routes ? MappedRoutes<R> extends infer O ? {
[P in keyof O]: O[P]
} : never : {} : never)
}
Вы можете проверить, что он работает так, как вы хотите (хотя и без вмешательства ArrayIndices
):
type Mapped = MappedRoutes<typeof routes>
/* type Mapped = {
path1: {};
path2: {
path3: {};
};
} */
Если T
присваивается Routes
, массиву {path: string, children?: Routes}
, тогда MappedRoutes<T>
имеет ключи в T[number]["path"]
... где T[number]
- это объединение типов элементов массива, а T[number]["path"]
- это объединение их свойств path
.
Затем для каждого из этих ключей K
мы получаем Extract<T[number], {path: K}>['children']
, которое является свойством children
, соответствующим этому path
. (Extract<T[number], {path: K}>
) принимает объединение элементов массива и извлекает только тот, который можно присвоить {path: K}
. И тогда мы получаем его свойство children
).
Мы проверяем это свойство children
. Это сам Routes
? Если это так, рекурсивно создайте MappedRoutes
его. Там происходит изрядное количество условного вывода типов, но в основном для того, чтобы я мог хранить типы в переменных типа, а не переписывать их. R
- это свойство children
, а O
- это сопоставленное свойство children
, если оно существует. Я прохожу через O
, чтобы тип Mapped
быстро рекурсивно расширялся до своей окончательной формы вместо того, чтобы оставить его как {path1: {}, path2: MappedRoutes<readonly blah blah yuck>}
.
Хорошо, надеюсь, это поможет; удачи!
Детская площадка ссылка на код