(arg: string) => void
和(arg: number) => void
的并集是(arg: never) => void
,因为没有值可以同时是string
和number
。但是在下面的示例中我该如何解决这个问题呢?
type Test<T, U> =
| {
func: (arg: T) => void;
data: T;
}
| {
func: (arg: U) => void;
data: U;
};
function test<T, U>(x: Test<T, U>) {
x.func(x.data);
}
在此示例中,
x.data
应该始终是正确的类型以满足函数参数,但打字稿看不到它并且会出现Argument of type 'string | number' is not assignable to parameter of type 'never'
错误。
编辑:我的用例如下:我们的应用程序有一些代码来处理页面之间的导航。每个可能的页面都由如下所示的内容表示:
type Route<T> = {
extractData: (url: string) => T;
encodeDataToURL: (data: T) => string;
getTitle: (data: T) => string;
render: (data: T) => DOM.Element;
}
路由器处理所有路由,因此调用当前路由的
extractData
会产生 HomePageData | SearchPageData | ...
。当我们调用 getTitle
或 render
时,因为它是生成数据的同一个对象,所以它应该仍然可以正常工作,但 typescript 似乎没有注意到这一点。
Typescript 就不是这样工作的。它只跟踪类型,而不跟踪变量之间的关系。如果您从对象中获取属性,Typescript 将不会记住您从哪里获取它。这只是类型的另一回事。
您的
Test
设计过于模糊,无法按原样使用。尝试将对象文字传递给 test
:
test({
data: "hello",
func: (s) => s // Parameter 's' implicitly has an 'any' type.
})
Typescript 不知道发生了什么,因为
Test
联合成员之间的唯一区别是抽象类型参数。它没有基础来选择您想要的联合成员,因此它无法将 func
的参数缩小到 string
和您未指定的其他抽象类型之间。
为了解决您的示例案例,
Test
中的联合和单独的泛型参数是完全多余的,当您摆脱它们时,问题就会消失。您提供的 Route
类型完全符合您的预期。
// no problems
function test<T>(x : Route<T>) {
x.render(x.extractData("imma string"));
}
test({
extractData: (url) => ({ stuff: "yes", junk: "no" }),
encodeDataToURL: (data) => data.stuff,
getTitle: (data) => data.junk,
render: (data) => new Element()
})
在实践中,如果您正在使用使用不同数据类型的
Route
的并集,并且您希望函数统一作用于所有 Route
,则理想情况下,函数签名将表示数据类型并不重要.
function render(route : Route<any>) : Element {
return route.render(route.extractData("gimme"));
}
type TargetRoute = Route<{ junk : number }> | Route<{ stuff : string }>
function renderTarget(target : TargetRoute) {
render(target);
// otherwise, you could do this:
test<any>(target);
// but if the function isn't acting on the generic data type anyway,
// you're obfuscating its purpose
}
最后,这个最小的情况不需要它们,但是如果您需要区分 Route
而无需先提取它们的数据,您可能会想要使用
discriminated unions。