typescript - 创建一个根据布尔参数返回类型的异步函数
我正在尝试在 TypeScript 中创建一个具有正确类型返回的异步函数。该函数将有一个布尔参数,用于决定函数的返回类型。我在网上搜索,似乎使用类型条件是可行的方法。
我实现代码的方式或多或少如下:
type Foo = {
propA: string
propB: string
}
type Bar = Omit<Foo, 'propB'> & {propC: string}
type ConditionalFooBar<T extends boolean> = T extends true? Foo:Bar
async function mainFunction<T extends boolean>(param:T) : Promise<ConditionalFooBar<T>>{
// Some function awaits happened here
if(param===true){
return {
propA: "a string",
propB: "a string"
}
}
return {
propA: "a string",
propC: "a string"
}
}
使用这段代码,TypeScript 编译器在第一个 return 语句处报了以下错误:
Type '{ propA: string; propB: string; }' is not assignable to type 'ConditionalFooBar<T>'.ts(2322)
我该如何解决这个问题?
答案#1
目前(截至 TypeScript 5.4),无法实现返回条件类型的泛型函数,使编译器能够准确地验证其类型安全性。在 microsoft/TypeScript#33912 上有一个长期未解决的功能请求,期望得到更好的实现,并且这个问题似乎是 TypeScript 5.5 的开发计划的一部分(见 microsoft/TypeScript#57475),同时在 microsoft/TypeScript#56941 上有一个拉取请求(pull request)试图解决它。如果该拉取请求或类似的请求被合并,那么你的代码可能会突然按照你编写的方式正常工作而不会报错。
在那种情况发生之前,如果你想在运行时保持你的函数不变,你需要使用类似类型断言的东西来防止编译器错误,选择不进行检查以避免可能的假阴性,而不是进行过于严格的检查导致假阳性:
async function mainFunction<T extends boolean>(param: T): Promise<ConditionalFooBar<T>> {
if (param === true) {
return {
propA: "a string",
propB: "a string"
} as ConditionalFooBar<T>
}
return {
propA: "a string",
propC: "a string"
} as ConditionalFooBar<T>
}
}
如果你现在需要编译器验证的类型安全,你需要从使用条件类型重构,转而使用索引访问类型,其中你的操作是以属性查找的方式编写,而不是使用 if/else 块。例如:
interface X {
true: Foo;
false: Bar;
}
async function mainFunction<T extends boolean>(param: T) {
const x: X = {
get true() {
return {
propA: "a string",
propB: "a string"
}
},
get false() {
return {
propA: "a string",
propC: "a string"
}
}
}
return x[`${param}`]
}
在这里你可以看到,X 接口只是一个从 string 类型的 "true" 映射到 Foo,以及从 string 类型的 "false" 映射到 Bar 的映射。在函数内部,我们有一个类型为 X 的值 x,使用 getter 意味着我们不必提前计算 true 和 false 两个分支。我们通过模板字符串索引 X,其中 true 或 false 会被序列化为 "true" 或 "false"。
如果你检查 mainFunction() 的调用签名,它是:
// function mainFunction<T extends boolean>(param: T): Promise<X[`${T}`]>
它返回一个索引访问,其中键是对应于序列化的 boolean 的模板字面量类型。你可以看到它按你想要的方式工作:
const a = mainFunction(true);
// ^? Promise<Foo>
const b = mainFunction(false);
// ^? Promise<Bar>
因此,当你调用它时,编译器会自动推断你期望的类型。但这个版本确实比原始实现更奇怪。仅仅为了类型安全去查找一个在对象中作为一对 getter 实现的序列化布尔键,可能不值得费那个功夫。
代码的 Playground 链接
