接下来,我将只关心打字而不是实现。所有功能都将是declare
d,就好像实际的实现在某个 JS 库中,这些是它们的声明文件 https://www.typescriptlang.org/docs/handbook/declaration-files/by-example.html#global-functions.
另外,你的isLeaf
函数可能应该键入为用户定义的类型保护 https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards其返回类型是类型谓词而不仅仅是boolean
。函数签名isLeaf: (value: S | Tree<S>) => value is S
与返回的类似boolean
,除了编译器实际上会理解if (isLeaf(x)) { x } else { x }
, the x
在真实的块中将是S
和x
在错误块中将是Tree<S>
.
好的,这里是:
方式Tree<X>
过于笼统,无法跟踪特定的键和值类型。所有编译器都知道该类型的值,例如,Tree<string>
,它是一个对象类型,其属性是以下任一类型string
or Tree<string>
。一旦你这样做了,就说:
const x: Tree<string> = { a: "", b: { c: "", d: { e: "" } } };
您已经丢弃了有关特定结构的所有详细信息以及有关任何特定子类型的所有详细信息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
但你想要一些更雄心勃勃的东西;看来你想要的叶子变换图不仅仅是来自特定类型X
到特定类型Y
,但您想指定一些通用类型函数如 type F<T extends X> = ...
并从类型映射叶子Z extends X
to F<Z>
.
在您的示例中,您的输入类型类似于Wrapped<any>
,你的输出类型函数看起来像type F<T extends Wrapped<any> = T["value"];
不幸的是,这种更通用的转换类型无法用 TypeScript 表达。你不能有像这样的类型函数type TransformType<T extends Tree<X>, X, F> = ...
where F
本身是一个带有参数的类型函数。这需要语言支持所谓的“更高种类的类型”。有一个长期开放的功能请求,位于微软/TypeScript#1213 https://github.com/microsoft/TypeScript/issues/1213,虽然拥有它们会令人惊奇,但看起来在可预见的未来不会发生。
你什么can要做的是设想特定类型的叶类型转换并实现特定版本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);
或者,正如您的评论中提到的,您可能想做相反的事情特别的通用接口/类,比如,Wrapper
...映射是要转换Z extends X
into 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
允许每个节点针对不同的更具体的转换进行测试的对。因此,举例来说,任何时候你发现"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
因此,您确实可以定义很多不同的甚至非常强大的树转换......只是不是问题所暗示的完全通用的高阶类型。希望其中之一能为您指明前进的方向。好的,祝你好运!
Playground 代码链接 https://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAKgThCAeGA+KBeKBvAsAKCiKgG0BrALigGdg4BLAOwHMBdKmKAH1gWTQDcBAL4ECAYwD2jWlAAeHPkloMW6LNigBDKgCJdAGigAjKpvF7DUACZmoES1GFOnQ-HIB0Wj8EkBVMEg4AGEtaggACgBKASgAejj7ODhJOAJPAC9YhKhGSSSUuCgxfFBIXi0ZADNUgFt4RBR7OWAIRmtqXkaADVQjbqMATXUcUgBpKCYoMggQSSrYdgJiWBIx1mbW9s7uqAB+KEGoCmXiGDWNiBa2jq7kXv2K6rqG-gv+ofQTwjOLt2E3NYIOIADZaBBQKoAV0Y4mA9GkUDolWoNTg9SUA0ORhguyuW1uryQvVQEVOREkxgAVhwBuSkXAUWjalQIgA3LQgqEOKDdKKYdCDAz0+jUAAyEC0VVZHK5PN2PCJvX5GHQsu5kx2BCiiiZLyUuI+h1Qbgk0lkwAAjJhRjooLoAIJWUyjCz24LBKy2UY83QAUQDuhczgBZpkwCRACYbcjnujXhErcKfkQIgoaHQmMwVehPCC2sxgAALZMrdk6qBszUZ1TMAVI8AQeaVzAYLC6FRZ3TatzASNebKJRhQ2rGCBpUr94wecSD3IjscTvseafWDwQOfD0fjkplaDwPXxvgASWschQRom+JunRmcwWDw0pAACpNGNNZs2YEsU6tn5drm2XlHm6C5jnpA84wxRBT3Pc5-0vE0REBYEwQhaFYXhRFY1RfUYLPFAsSxK9ANuO9m16Mlf0pGlYDpX87yoMZS2IUUJSlGVOW5KhKhAHNKy46BRV5bVdSg15YMIxDTXwUEwk6AB1RlAnHFB0DwX8pHDOAoThVIIjAKFjBBehxAEuUOH5TRRHwGytNkAB3G0NJWGpJDMekVmMcEqEYCAnKUrQVLgCJdG8jIgzCKB7OAKIWKIYR4uiyQHN8-yoEC4LQtqSRJEizoYri5CShiqAHOjLAcOZCSzyolYHKMXR1QgKw0x4xg+KoORq0yoIkF49RczfWhKnEJsFl6nd8BiEoMLhBF3yLSprHzAAhLQMnZQS9HC3QrKcAg5qwxblvzABZXKtos+0cry-abKW9o1o2iJyo8NyV3BGb8EelaIAuyRXv7KQHO+3dGyeXCj0QTKL15EZNBIV8pnIhZv2+FZ4IAgkdkeSa4BQJHWC+CDGXEvhYax-okNslC5PQmF5uwsmoegiBKaIowSJxj973huriBo2kkrYyVpSgK7uO0Dr+Oa6tulEyHqop5SpPhmSw1kKFnPpNyPN-LyfPtXbtAK81gCSxL6RBvRbvy5Lw2TUN8E1iMoQqhlDzZzKBaIKEjDa6XOvkatOxYes92bbq23bMPmG7aa3F+57Nvd97cs+uAPGa77k-+y605B7PBLBl2y4IPclbw-6grhs7NhvUheKMXjiec8Y315r8f0xsDryAs6SAABg2A4-RaRk4SQM6jBIc51hbjriZIS0Ngxs4WeVxAztr+fWCMM70GdiuIePcUxYddpINZ1TjyMAB5BGRXPjiJfTXj+O64S7-pKq6lZdMx5+L3xECfco19mQOmSFoEAMBJA7zANPBuQEoGMhAEgM+7EqiX2sBAuoqlm5B1QMQnWv41id1RlAM67AqF91Ip0TBF8r6b3wQTJgVRxxQDvm+DhRRH77HpCQbh98165AgGyThwgSBbkXKwEoQJ6bQCOgtT25Nt67wPsg24qCYEYJftg5hXsCEdUXiAYhpJ6RC1gElDwti-7onHNQKgZ1FZ4OhjXRBMAcQsPRDo2B8Da6Hxpq7KAtRagxh8WzBBER-b0k0KLV+gcP5dWrLoO29Zo5thurlKw9iWRv1tjk-ilpIwAGYnBJXifogB7Vg5f06GFDaQZVQhxjsbJpRg8k1PaRFfidANQ2W+mEmcKV4hDgXFNYZH1vJFByMYXK+ZKglCgEAA