Как уже упоминалось, в настоящее время невозможно объединить строковые литералы на уровне типа. Были предложения, которые могли бы позволить это, например, предложение разрешить увеличение ключей при отображаемых типах и предложение проверить строковые литералы с помощью регулярного выражения , но пока это невозможно.
Вместо того, чтобы представлять пути как пунктирные строки, вы можете представлять их как кортежи строковых литералов. Таким образом, "a"
становится ["a"]
, а "nest.c"
становится ["nest", "c"]
. Во время выполнения достаточно просто выполнить преобразование между этими типами с помощью методов split()
и join()
.
Так что вы можете захотеть что-то вроде Paths<T>
, которое возвращает объединение всех путей для данного типаT
, или, возможно, Leaves<T>
, то есть только те элементы Paths<T>
, которые указывают на сами необъектные типы. Нет встроенной поддержки для такого типа;в библиотеке ts-toolbelt есть , но, поскольку я не могу использовать эту библиотеку на Playground , я скрою свою собственную здесь.
Имейте в виду: Paths
и Leaves
по своей природе являются рекурсивными, что может быть очень сложным для компилятора. И рекурсивные типы, необходимые для этого, также официально не поддерживаются в TypeScript. То, что я представлю ниже, рекурсивно в этом нереалистичном / не очень поддерживаемом способе, но я пытаюсь предоставить вам способ указать максимальную глубину рекурсии.
Вот и мы:
type Cons<H, T> = T extends readonly any[] ?
((h: H, ...t: T) => void) extends ((...r: infer R) => void) ? R : never
: never;
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...0[]]
type Paths<T, D extends number = 10> = [D] extends [never] ? never : T extends object ?
{ [K in keyof T]-?: Cons<K, Paths<T[K], Prev[D]>> | Paths<T[K], Prev[D]> }[keyof T]
: [];
type Leaves<T, D extends number = 10> = [D] extends [never] ? never : T extends object ?
{ [K in keyof T]-?: Cons<K, Leaves<T[K], Prev[D]>> }[keyof T]
: [];
Цель Cons<H, T>
- взять любой тип H
и тип кортежа T
и создать новый кортеж с H
, добавленным к T
. Так что Cons<1, [2,3,4]>
должно быть [1,2,3,4]
. Реализация использует кортежей отдыха / распространения . Это понадобится нам для построения путей.
Тип Prev
- это длинный кортеж, который можно использовать для получения предыдущего числа (до максимального значения). Так что Prev[10]
равно 9
, а Prev[1]
равно 0
. Нам понадобится это, чтобы ограничить рекурсию, когда мы углубимся в дерево объектов.
Наконец, Paths<T, D>
и Leaves<T, D>
реализуются путем перехода к каждому типу объекта T
и сбора ключей, иCons
их на Paths
и Leaves
свойств этих ключей. Разница между ними заключается в том, что Paths
также напрямую включает подпути в объединении. По умолчанию параметр глубины D
равен 10
, и на каждом шаге вниз мы уменьшаем D
на единицу до тех пор, пока не попытаемся пройти 0
, после чего мы прекращаем повторение.
Хорошо, давайте проверим это:
type NestedObjectPaths = Paths<NestedObjectType>;
// type NestedObjectPaths = [] | ["a"] | ["b"] | ["c"] |
// ["nest"] | ["nest", "c"] | ["otherNest"] | ["otherNest", "c"]
type NestedObjectLeaves = Leaves<NestedObjectType>
// type NestedObjectLeaves = ["a"] | ["b"] | ["nest", "c"] | ["otherNest", "c"]
И чтобы увидеть полезность, ограничивающую глубину, представьте, что у нас есть такой тип дерева:
interface Tree {
left: Tree,
right: Tree,
data: string
}
Ну, Leaves<Tree>
очень много:
type TreeLeaves = Leaves<Tree>; // sorry, compiler ?⌛?
// type TreeLeaves = ["data"] | ["left", "data"] | ["right", "data"] |
// ["left", "left", "data"] | ["left", "right", "data"] |
// ["right", "left", "data"] | ["right", "right", "data"] |
// ["left", "left", "left", "data"] | ... 2038 more ... | [...]
, и компилятору потребуется много времени, чтобы его сгенерировать, и производительность вашего редактора внезапно станет очень-очень плохой. Давайте ограничимся чем-то более управляемым:
type TreeLeaves = Leaves<Tree, 3>;
// type TreeLeaves2 = ["data"] | ["left", "data"] | ["right", "data"] |
// ["left", "left", "data"] | ["left", "right", "data"] |
// ["right", "left", "data"] | ["right", "right", "data"]
Это заставит компилятор перестать смотреть на глубину 3, поэтому все ваши пути имеют длину не более 3.
Итак, это работает. Вполне вероятно, что ts-toolbelt или какая-то другая реализация может позаботиться о том, чтобы у компилятора не было сердечного приступа. Так что я не обязательно сказал бы, что вы должны использовать это в своем производственном коде без значительного тестирования.
Но в любом случае вот ваш желаемый тип, при условии, что вы имеете и хотите Paths
:
type MyGenericType<T extends object> = {
keys: Array<Paths<T>>;
};
const test: MyGenericType<NestedObjectType> = {
keys: [['a'], ['nest', 'c']]
}
Надеюсь, это поможет;удачи!
Ссылка на код