我想制作用于向数组添加项目的通用函数。我还希望它通过智能等进行类型保护。
我是这样制作smt的:
interface IOne {
id: number;
text: string;
}
interface ITwo {
id: number;
data: string;
}
interface IItems {
one: IOne[];
two: ITwo[]
}
const list: IItems = {
one: [],
two: []
}
function add(type: 'one', data: IOne): void
function add(type: 'two', data: ITwo): void
function add(type: 'one'|'two', data: IOne|ITwo) {
list[type].push(data)
}
但出现错误:
'IOne 类型的参数 | ITwo' 不可分配给类型为 'IOne & ITwo' 的参数。 “IOne”类型不能分配给“IOne & ITwo”类型。 “IOne”类型中缺少属性“data”,但“ITwo”类型中需要。
如何处理?
答案 0 :(得分:3)
从根本上说,这不是 TypeScript 编译器可以验证为安全的。 type
和 data
都属于 union 类型,但编译器将无法理解这些联合是否相关;它会将它们视为独立的,因此它认为 type
可能是 "one"
而 data
是 ITwo
,并抱怨这样做不安全。
microsoft/TypeScript#30581 有一个未解决的问题,请求支持处理此类相关联合,但它主要是记录人们何时遇到问题的仓库,并提到最好的办法就是使用type assertion 并继续前进。
您编写的特定 overload 实现实际上不是类型安全的,因为实现签名明确指出 type
和 data
是不相关的联合:
function add(type: 'one' | 'two', data: IOne | ITwo) {
list[type].push(data); // error!
}
这是一个合理的错误;即使原则上,编译器也不知道 list[type]
会接受 data
。由于您碰巧从调用签名中知道这没问题,您可以使用类型断言来抑制错误。例如:
function add(type: 'one' | 'two', data: IOne | ITwo) {
(list[type] as (IOne | ITwo)[]).push(data); // okay
}
此处您已告诉编译器 list[type]
是一个数组,其元素可以是 IOne
或 ITwo
。这在技术上是不正确的(list.one
和 list.two
都不应包含 IOne
和 ITwo
元素),但只要您确定实施是正确的。这通常是类型断言的情况;您承担了一些保证类型安全的责任,因为编译器不能。
如果您想在实现中实际表示 type
和 data
之间的相关性(因此您甚至不需要重载),您可以使用类型为tuple types 的并集:
declare function add(...args: [type: "one", data: IOne] | [type: "two", data: ITwo]): void;
您可以验证这只能以您想要的方式调用:
add("one", { id: 1, text: "" }); // okay
add("two", { id: 2, data: "" }); // okay
add("one", { id: 2, data: "" }); // error
但是由于 microsoft/TypeScript#30581 中提到的限制,编译器在实现中仍然无法遵循相关性:
function add(...args: [type: "one", data: IOne] | [type: "two", data: ITwo]) {
list[args[0]].push(args[1]); // error!
}
在这里,至少,您可以通过编写冗余代码来解决这个问题,强制编译器通过 control flow analysis 遍历 "one"
和 "two"
的不同情况:
function add(...args: [type: "one", data: IOne] | [type: "two", data: ITwo]) {
if (args[0] === "one") {
list[args[0]].push(args[1]); // okay
} else {
list[args[0]].push(args[1]); // okay
}
}
但是这种冗余很烦人,而且不能很好地扩展。如果让编译器验证类型安全比开发人员的生产力和编写惯用的 JavaScript 更重要,我只会建议这样的事情。相反,即使在这种情况下,最合理的解决方案也只是使用类型断言:
function add(...[type, data]: [type: "one", data: IOne] | [type: "two", data: ITwo]) {
(list[type] as (IOne | ITwo)[]).push(data); // okay
}
这会让你回到非常像重载版本的东西。哦,好吧!