我希望有一个函数,它接受两个联合类型作为参数,但是强制它们成为相同的子类型...而不必在调用函数之前亲自检查类型。
这是我尝试过的:
背景故事
为简单起见,我将定义一些类型:
type Foo = number;
type Bar = [number, number?];
type FooBar = Foo | Bar;
我有一个功能:
function fn(x: FooBar, y: FooBar) {}
我要强制x
和y
是同一类型。 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
}
反正有做我想做的事吗?也许使用其他模式或方法。
答案 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
,现在它总是与foo
和bar
匹配,就好像它们是可互换的。
答案 1 :(得分:1)
在调用fn(x, y)
之前,您必须进行某种类型检查,因为所有编译器都知道x
和y
都是foobar
类型,并且您已声明fn
,使其仅接受两个foo
参数或两个bar
参数,而不能同时接受。 您知道x
和y
都将是number
(又名foo
),但这是因为您已经完成了{{ 1}},然后得出结论,这些类型都可以。编译器不是那么聪明。就其所知,JSON.parse()
可以是x
,foo
可以是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
}
可以安全地应用于fn
和x
,即使通常如果不可能的话。请注意,作为断言,您放弃了编译器的安全性,以换取能够随意编写所需代码的自由。
就我个人而言,我可能会离开冗余类型检查,然后继续进行。它具有编译器强制类型安全的优点。但这取决于你。希望能有所帮助;祝你好运!