我的UnwrapArray<T>
类型给我T
的{{1}}。因此,当我执行Array<T>
时,应该可以解决为Array<UnwrapArray<T>>
,但是不会发生。
错误消息从字面上告诉我该定义的相反含义:
类型'UnwrapArray []'不能分配给类型'T'。
这是一个完整的例子:
T
在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)
}
}
方法内的赋值中可以观察到错误。
我正在寻找一种解释,说明为什么这种方法行不通,并寻求有关如何更改类型以使预期行为起作用的说明。
答案 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[]
,您更希望T
是Array<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.prop
,Array<1 | "a">
的类型为foo.structs
,而Array<{foo: 1 | "a"}>
可以很好地将一个转换为另一个。因此,除非您的用例要求通用参数本身必须是某种数组类型,否则我建议您继续这样做。
好的,希望其中一个能帮上忙。祝你好运!