这就是我所看到的情况。让我们使用这些定义:
type Callback<T1, T2> = (y: T1, z: T2) => void;
type First = Callback<number, 'first'>;
type Second = Callback<string, 'second'>;
首先,我绝对怀疑您想要函数的联合而不是函数的交集。请注意,这样的函数联合本质上是无用的:
const unionTest = (x: First | Second) => {
// x is *either* a First *or* it is a Second,
// *but we don't know which one*. So how can we ever call it?
x(1, "first"); // error!
// Argument of type '1' is not assignable to parameter of type 'never'.
x("2", "second"); // error!
// Argument of type '"2"' is not assignable to parameter of type 'never'.
}
unionTest() 函数与您的 test() 相同,但它不能与 x 做任何事情,x 只知道是 First 或 @987654336 @。如果您尝试调用它,无论如何都会出错。 union 函数只能安全地作用于其参数的交集。对此的一些支持是added in TS3.3,但在这种情况下,参数类型是互斥的,因此只有可接受的参数类型为never...所以x 是不可调用的。
我怀疑这种相互不兼容的功能的结合是否是任何人想要的。并集和交集的对偶性以及函数类型的contravariance 相对于它们的参数类型令人困惑且难以谈论,但区别很重要,所以我觉得这一点值得深究。这个工会就像发现我必须安排与某人会面,该人要么周一有空或周二有空,但我不知道哪个.我想如果我可以在星期一 和 星期二举行会议,那将是可行的,但假设这没有意义,我就被困住了。与我会面的人是工会,而我会面的那一天是一个十字路口。做不到。
相反,我认为您想要的是函数的交集。这对应于overloaded 函数;你可以用两种方式称呼它。看起来像这样:
const intersectionTest = (x: First & Second) => {
// x is *both* a First *and* a Second, so we can call it either way:
x(1, "first"); // okay!
x("2", "second"); // okay!
// but not in an illegal way:
x(1, "second"); // error, as desired
x("2", "first"); // error, as desired
}
现在我们知道x 既是First,又是 Second。你可以看到你可以把它当作First 或Second 来对待并且没问题。但是,您不能将其视为某种奇怪的混合体,例如x(1, "second"),但大概这就是您想要的。现在我正在安排与某人的会议,他将在 周一 和 周二有空。如果我问那个人在哪一天安排会议,她可能会说“星期一或星期二我都可以”。见面的那天是工会,和我见面的人是十字路口。这行得通。
所以现在我假设您正在处理函数的交集。不幸的是编译器doesn't automatically synthesize the union of parameter types for you,你仍然会得到那个“隐式任何”错误。
// unfortunately we still have the implicitAny problem:
intersectionTest((x, y) => { }) // error! x, y implicitly any
您可以手动将函数的交集转换为作用于参数类型联合的单个函数。但是对于两个受约束的参数,表达这一点的唯一方法是使用rest arguments and rest tuples。我们可以这样做:
const equivalentToIntersectionTest = (
x: (...[y, z]: Parameters<First> | Parameters<Second>) => void
) => {
// x is *both* a First *and* a Second, so we can call it either way:
x(1, "first"); // okay!
x("2", "second"); // okay!
// but not in an illegal way:
x(1, "second"); // error, as desired
x("2", "first"); // error, as desired
}
就其行为方式而言,这与intersectionTest() 相同,但现在参数具有已知类型并且可以根据上下文键入比any 更好的类型:
equivalentToIntersectionTest((y, z) => {
// y is string | number
// z is 'first' | 'second'
// relationship gone
if (z === 'first') {
y.toFixed(); // error!
}
})
不幸的是,正如您在上面看到的,如果您使用(y, z) => {...} 实现该回调,则y 和z 的类型将成为独立的联合体。编译器忘记了它们是相互关联的。一旦您将参数列表视为单独的参数,您就会失去相关性。我已经看到足够多的问题想要解决这个问题,我 filed an issue about it,但目前还没有直接的支持。
让我们看看如果我们不立即分离参数列表会发生什么,通过将其余参数分散到一个数组中并使用它:
equivalentToIntersectionTest((...yz) => {
// yz is [number, "first"] | [string, "second"], relationship preserved!
好的,那很好。现在yz 仍在跟踪约束。
这里的下一步是尝试通过类型保护测试将yz 缩小到工会的一个或另一个分支。最简单的方法是如果yz 是discriminated union。它是,但不是因为y(或yz[0])。 number 和 string 不是字面量类型,不能直接用作判别式:
if (typeof yz[0] === "number") {
yz[1]; // *still* 'first' | 'second'.
}
如果你必须检查yz[0],你必须实现你自己的type guard function 来支持它。相反,我建议打开z(或yz[1]),因为"first" 和"second" 是可用于区分联合的字符串文字:
if (yz[1] === 'first') {
// you can only destructure into y and z *after* the test
const [y, z] = yz;
y.toFixed(); // okay
z === "first"; // okay
} else {
const [y, z] = yz;
y.toUpperCase(); // okay
z === "second"; // okay
}
});
请注意,在将yz[1] 与'first' 进行比较后,yz 的类型不再是联合,因此您可以以更有用的方式解构为y 和z。
好的,哇。好多啊。 TL;DR 代码:
const test = (
x: (...[y, z]: [number, "first"] | [string, "second"]) => void
) => { }
test((...yz) => {
if (yz[1] === 'first') {
const [y, z] = yz;
y.toFixed();
} else {
const [y, z] = yz;
y.toUpperCase(); // okay
}
});
希望有所帮助;祝你好运!
Link to code