具有联合类型的Typescript重载函数

时间:2018-10-12 14:41:18

标签: typescript

我希望有一个函数,它接受两个联合类型作为参数,但是强制它们成为相同的子类型...而不必在调用函数之前亲自检查类型。

这是我尝试过的:

背景故事

为简单起见,我将定义一些类型:

type Foo = number;
type Bar = [number, number?];
type FooBar = Foo | Bar;

我有一个功能:

function fn(x: FooBar, y: FooBar) {}

我要强制xy是同一类型。 IE允许fn(Foo, Foo)fn(Bar, Bar),但不允许fn(Foo, Bar)fn(Bar, Foo)。所以我决定重载该功能。

function fn(x: Foo, y: Foo): void;
function fn(x: Bar, Bar): void;
function fn(x: FooBar, y: FooBar): void {}

我从API请求数据。我从API获得了两个对象,我怀疑这是否重要,但是它们将是同一类型,要么都是Foo要么都是Bar。这取决于我的要求。

问题

当我调用函数时,它会发出抱怨(我使用JSON.parse作为模拟API调用的方式)

const x: FooBar = JSON.parse("1");
const y: FooBar = JSON.parse("2");
fn(x, y);
  

类型'FooBar'的参数不能分配给类型的参数   '[数字,数字?]'。类型“数字”不可分配给类型   “ [数字,数字?]”。

如果我事先检查类型,它会起作用:

(旁注:希望使用声明的类型时,有一种更简便的方法来检查类型)

if(typeof x === 'number' && typeof y === 'number') fn(x, y);
else if(Array.isArray(x) && Array.isArray(y)) fn(x, y);

但是这似乎很愚蠢,我不想在调用函数之前检查类型,而最终调用将完全相同。

使用or令人惊讶的是不起作用(即使我仍然不想这样做)

if((typeof x === 'number' && typeof y === 'number') ||
  (Array.isArray(x) && Array.isArray(y))) {
    fn(x, y); // same error as above
}

StackBlitz example

反正有做我想做的事吗?也许使用其他模式或方法。

2 个答案:

答案 0 :(得分:1)

这里的问题是,在您提供的StackBlitz示例中,您的fn(foobar,foobar)仍将对fn(foo,bar)执行。编辑器中报告了错误,但是如果在console.log的正文中放置fn,则会看到打印出来的错误。如果您通过fn(1, [2, 3]),您会看到它打印出来没有问题。

为了在此处将使用限制为特定的匹配,您可以进行类型检查。

type foo = number;
type bar = [number, number?];
type foobar = foo | bar;

function fn(x: foobar, y: foobar): void {
  if (typeof x != typeof y) return;
  console.log(x + " __ " + y);
}

const x: foobar = JSON.parse("1");
const y: foobar = JSON.parse("2");

// Executes
fn(x, y);
// Doesn't execute
fn(2, [4, 5]);

之所以出现困难,是因为您要定义类型foobar,现在它总是与foobar匹配,就好像它们是可互换的。

答案 1 :(得分:1)

在调用fn(x, y)之前,您必须进行某种类型检查,因为所有编译器都知道xy都是foobar类型,并且您已声明fn,使其仅接受两个foo参数或两个bar参数,而不能同时接受。 知道xy都将是number(又名foo),但这是因为您已经完成了{{ 1}},然后得出结论,这些类型都可以。编译器不是那么聪明。就其所知,JSON.parse()可以是xfoo可以是y,然后bar不起作用。也就是说:fn(x, y)上没有类型检查的错误是一个很好的错误,可以帮助您编写更好的代码。

因此,在fn(x,y)fn上调用重载的x的最直接方法是您在此处进行的类型检查:

y

是的,这是多余的。尝试将它们折叠为一个语句:

if (typeof x === 'number' && typeof y === 'number') fn(x, y);
else if (Array.isArray(x) && Array.isArray(y)) fn(x, y);
如您所见,

无效。正确处理联合类型的呼叫签名是outstanding issue。特别是,if ((typeof x === 'number' && typeof y === 'number') || (Array.isArray(x) && Array.isArray(y))) { fn(x, y); // error } x的类型是y的相关子类型(意味着它们都是foobar或都是foo),但是编译器是无法处理。我已经经历了无数次,甚至遇到过suggested a way of dealing with this(可怜的我,它不会被采纳),但是目前还没有很好的解决方案。

如果您不想使用多余的运行时代码(以换取一些编译时的混乱),您可以在此处执行的最简单的操作是使用type assertion

bar

}

这里,您断言在类型检查块中,if ((typeof x === 'number' && typeof y === 'number') || (Array.isArray(x) && Array.isArray(y))) { (fn as (x: foobar, y: foobar) => void)(x, y); // okay } 可以安全地应用于fnx,即使通常如果不可能的话。请注意,作为断言,您放弃了编译器的安全性,以换取能够随意编写所需代码的自由。

就我个人而言,我可能会离开冗余类型检查,然后继续进行。它具有编译器强制类型安全的优点。但这取决于你。希望能有所帮助;祝你好运!