为什么不展开和重新包装类型数组可以让我恢复原始类型?

时间:2019-09-08 12:28:55

标签: typescript

我的UnwrapArray<T>类型给我T的{​​{1}}。因此,当我执行Array<T>时,应该可以解决为Array<UnwrapArray<T>>,但是不会发生。

错误消息从字面上告诉我该定义的相反含义:

  

类型'UnwrapArray []'不能分配给类型'T'。

这是一个完整的例子:

T

Playground link is over here.

type UnwrapArray<T> = T extends Array<infer V> ? V : never type SomeStructure<T> = { foo: T } class Foo<T extends Array<any>> { prop!: T structs!: Array<SomeStructure<UnwrapArray<T>>> setProp () { this.prop = this.structs.map(x => x.foo) } } 方法内的赋值中可以观察到错误。

我正在寻找一种解释,说明为什么这种方法行不通,并寻求有关如何更改类型以使预期行为起作用的说明。

1 个答案:

答案 0 :(得分:1)

我不确定为什么您希望T成为any[]的子类型。 any[]的许多子类型会违反您对setProp()会做的假设。我能想到的最简单的示例是tuple type,如以下示例所示:

const foo = new Foo<[1, "a"]>();

请注意,foo已将T指定为元组[1, "a"]。这意味着foo.prop必须是由两个元素组成的数组,其中第一个元素是1,而第二个元素是"a"。但是,根据您的展开/修改/包装定义定义的foo.structs的类型为Array<{foo: 1 | "a"}>。因此,以下分配有效:

foo.structs = [{ foo: "a" }, { foo: 1 }, { foo: "a" }]; // Array<{foo: 1 | "a"}>;

然后我们调用setProp(),并检查prop

foo.setProp();
console.log(foo.prop); // [1, "a"] at compile time, ["a", 1, "a"] at runtime

糟糕,foo.prop的类型应该为[1, "a"],但运行时的值为["a", 1, "a"]。太糟糕了,错误在this.prop = this.structs.map(x => x.foo)行中。

错误消息:

'UnwrapArray<T>[]' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'any[]'.

是编译器说它知道UnwrapArray<T>[]可分配给any[],但是T可能是any[]的{​​{1}}的其他子类型无法分配...而这正是UnwrapArray<T>[]是元组类型时发生的情况。


从此处可以(但不一定建议)沿用mapped arrays/tuples来表示T类型的路线,例如:

structs

这将防止较早定义structs!: { [K in keyof T]: { foo: T[K] } } 的错误

foo.structs

这更安全。实际上,我认为这现在可能是完全类型安全的。不幸的是,编译器仍然无法在const foo = new Foo<[1, "a"]>(); foo.structs = [{ foo: "a" }, { foo: 1 }, { foo: "a" }]; // error, as desired 内进行验证:

setProp()

这里的问题是,setProp() { this.prop = this.structs.map(x => x.foo); // still error } 的标准库类型不具备处理映射的数组/元组类型的功能,如果尝试编写可进行此操作的类型,您会发现编译器将不容易确信Array.prototype.map()会将x => x.foo映射到typeof this.structs。一旦您确信自己确实是安全的(或者至少在实践中足够安全),您也可以跳过这里所有的高阶杂耍,并使用type assertion

T

但是我认为您实际上并不打算将this.prop = this.structs.map(x => x.foo) as T; 用作T扩展名。对于某些元素类型any[],您更希望TArray<V>。在这种情况下,我们应该将该元素设为通用类型:

V

现在可以正常工作了,并且非常简单,因为类型更简单。 class Foo<T> { prop!: T[]; structs!: { foo: T }[]; setProp() { this.prop = this.structs.map(x => x.foo); // okay } } const foo = new Foo<1 | "a">(); foo.structs = [{ foo: "a" }, { foo: 1 }, { foo: "a" }]; // Array<{foo: 1 | "a"}>; foo.setProp(); console.log(foo.prop); // Array<1 | "a"> at compile time, which matches ["a", 1, "a"] at runtime 的类型为foo.propArray<1 | "a">的类型为foo.structs,而Array<{foo: 1 | "a"}>可以很好地将一个转换为另一个。因此,除非您的用例要求通用参数本身必须是某种数组类型,否则我建议您继续这样做。


好的,希望其中一个能帮上忙。祝你好运!

Link to code