В дальнейшем я буду беспокоиться только о типах, а не о реализации. Все функции будут просто declare
d, как будто фактические реализации находятся в некоторой библиотеке JS, и это их файлы объявлений .
Кроме того, ваши isLeaf
функции, вероятно, должны быть напечатаны как определяемые пользователем типы защиты , возвращаемый тип которых - предикат типа вместо просто boolean
. Сигнатура функции isLeaf: (value: S | Tree<S>) => value is S
аналогична сигнатуре, которая возвращает boolean
, за исключением того, что компилятор фактически поймет, что в if (isLeaf(x)) { x } else { x }
x
в блоке true будет S
и x
в false блок будет Tree<S>
.
Хорошо, вот так:
Тип Tree<X>
является слишком общим для отслеживания определенных c типов ключей и значений. Все, что компилятор знает о значении типа, скажем, Tree<string>
, - это то, что это тип объекта, свойства которого имеют тип string
или Tree<string>
. Как только вы это сделаете, скажем так:
const x: Tree<string> = { a: "", b: { c: "", d: { e: "" } } };
Вы выбросили все детали о конкретной структуре, а также все детали о любых c подтипах string
на листьях:
x.a.toUpperCase(); // error
x.z; // no error
Если все, о чем вы заботились, это придумать преобразование типов, которое поддержало бы структуру вложенного ключа и превратило некоторый подтип Tree<X>
в подтип Tree<Y>
с той же формой, вы могли бы сделай это. Но в самой простой реализации все листы получившегося дерева будут иметь тип Y
, а не какой-либо более узкий тип. Вот как я бы это написал:
type TransformTree<T extends Tree<X>, X, Y> = { [K in keyof T]:
T[K] extends X ? Y :
T[K] extends Tree<X> ? TransformTree<T[K], X, Y> :
T[K];
};
declare function transformTree<X, Y, TX extends Tree<X>>(
obj: TX,
transform: (value: X) => Y,
isLeaf: (value: X | Tree<X>) => value is X
): TransformTree<TX, X, Y>;
И вы можете увидеть, как это работает на простом примере, подобном этому:
const t1 = { a: "A", b: { c: "CC", d: { e: "EEE" } } };
const t2 = transformTree(t1,
(x: string) => x.length,
(v): v is string => typeof v === "string"
);
t2.a; // number
t2.b.c; // number
t2.b.d.e; // number
Но вы хотите что-то значительно более амбициозное здесь ; кажется, что вы хотите иметь карту преобразования листа не только от определенного c типа X
к конкретному c типу Y
, но вы хотите указать некоторую функцию общего типа, такую как type F<T extends X> = ...
и сопоставьте лист от типа Z extends X
до F<Z>
.
В вашем примере ваш тип ввода похож на Wrapped<any>
, а ваша функция типа вывода будет выглядеть как type F<T extends Wrapped<any> = T["value"];
К сожалению, этот более общий тип преобразования не может быть выражен в TypeScript , Вы не можете иметь функцию типа, например type TransformType<T extends Tree<X>, X, F> = ...
, где F
сама является функцией типа, которая принимает параметр. Это потребовало бы, чтобы язык поддерживал то, что известно как «типы с более высоким родом». Для этого существует давний запрос на открытую функцию по адресу microsoft / TypeScript # 1213 , и хотя было бы удивительно иметь их, не похоже, что это произойдет в обозримом будущем.
То, что вы можете сделать, - это представить конкретные типы преобразований конечных типов и реализовать для них определенные c версии TransformTree
. Например, если ваше отображение типа листа просто индексирует в одно свойство, такое как type F<T extends Record<K, any>, K extends PropertyKey> = T[K]
, как в вашем случае Unwrap
, то вы можете написать его так:
type TransformTreeIdx<T, X, K extends keyof X> = { [P in keyof T]:
T[P] extends X ? X[K] :
TransformTreeIdx<T[P], X, K>;
};
declare function transformTreeIdx<TX, X, K extends keyof X>(
obj: TX,
key: K,
isLeaf: (value: any) => value is X
): TransformTreeIdx<TX, X, K>;
И затем использовать его:
const w = {
foo: {
bar: new Wrapper("baz" as const),
},
cow: new Wrapper("moo" as const),
};
const w2 = transformTreeIdx(
w, "value", (x: any): x is Wrapper<any> => x instanceof Wrapper
);
handleBaz(w2.foo.bar);
handleMoo(w2.cow);
Или, как упоминалось в ваших комментариях, вы можете сделать обратное с определенным generi c интерфейсом / классом, как, скажем, Wrapper
. .. сопоставление заключается в преобразовании Z extends X
в Wrapper<Z>
:
type TransformTreeWrap<T, X> = { [P in keyof T]:
T[P] extends X ? Wrapper<T[P]> :
TransformTreeWrap<T[P], X>;
};
declare function transformTreeWrap<TX, X, K extends keyof X>(
obj: TX,
isLeaf: (value: any) => value is X
): TransformTreeWrap<TX, X>;
и использовании его:
const u = {
foo: {
bar: "baz" as const,
},
cow: "moo" as const,
};
const u2 = transformTreeWrap(
u, (x: any): x is string => typeof x === "string"
);
handleBaz(u2.foo.bar.value);
handleMoo(u2.cow.value);
Или, возможно, у вас есть массив isLeaf
/ transform
пар, которые позволяют каждому узлу быть проверены на различные более специфичные c преобразования. Так, например, каждый раз, когда вы находите значение "moo"
в дереве, вы выводите number
, и каждый раз, когда вы обнаруживаете значение "baz"
в дереве, вы выводите boolean
.
Тогда ваш набор текста может выглядеть следующим образом:
type TransformTreeMap<T, M extends [any, any]> = { [K in keyof T]:
T[K] extends M[0] ? Extract<M, [T[K], any]>[1] :
TransformTreeMap<T[K], M> };
type IsLeafAndTransformer<I, O> = {
isLeaf: (x: any) => x is I,
transform: (x: I) => O
}
type TransformArrayToMap<M extends Array<IsLeafAndTransformer<any, any>>> = {
[K in keyof M]: M[K] extends IsLeafAndTransformer<infer I, infer O> ?
[I, O] : never }[number]
declare function transformTreeMap<T, M extends Array<IsLeafAndTransformer<any, any>>>(
obj: T,
...transformers: M
): TransformTreeMap<T, TransformArrayToMap<M>>;
и вы используете его:
const mm = transformTreeMap(u,
{ isLeaf: (x: any): x is "moo" => x === "moo", transform: (x: "moo") => 123 },
{ isLeaf: (x: any): x is "baz" => x === "baz", transform: (x: "baz") => true }
);
mm.cow // number
mm.foo.bar // boolean
Таким образом, существует множество различных и даже довольно мощных преобразований дерева, которые вы можете определить. .. только не полностью обобщенный c тип высшего порядка, подразумеваемый этим вопросом. Надеюсь, один из них даст вам путь вперед. Хорошо, удачи!
Детская площадка ссылка на код