打字稿 - 为函数分配不同的参数

时间:2021-06-02 13:27:20

标签: typescript

我想制作用于向数组添加项目的通用函数。我还希望它通过智能等进行类型保护。

我是这样制作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”类型中需要。

Fiddle

如何处理?

1 个答案:

答案 0 :(得分:3)

从根本上说,这不是 TypeScript 编译器可以验证为安全的。 typedata 都属于 union 类型,但编译器将无法理解这些联合是否相关;它会将它们视为独立的,因此它认为 type 可能是 "one"dataITwo,并抱怨这样做不安全。

microsoft/TypeScript#30581 有一个未解决的问题,请求支持处理此类相关联合,但它主要是记录人们何时遇到问题的仓库,并提到最好的办法就是使用type assertion 并继续前进。


您编写的特定 overload 实现实际上不是类型安全的,因为实现签名明确指出 typedata 是不相关的联合:

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] 是一个数组,其元素可以是 IOneITwo。这在技术上是不正确的(list.onelist.two 都不应包含 IOneITwo 元素),但只要您确定实施是正确的。这通常是类型断言的情况;您承担了一些保证类型安全的责任,因为编译器不能。


如果您想在实现中实际表示 typedata 之间的相关性(因此您甚至不需要重载),您可以使用类型为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
}

这会让你回到非常像重载版本的东西。哦,好吧!

Playground link to code