TypeScript函数如何根据参数对象值返回不同类型?析重载异常
TypeScript函数根据参数值动态返回类型问题
我编写了一个post函数,期望根据meta中的clean值确定其返回类型,代码如下:
interface MetaConfig<C extends boolean = boolean> { clean?: C } interface ResponseData<T> { code: number data: T message: string } interface Login { info: any } function post<R = any>(url: string, meta?: MetaConfig<true>): R function post<R = any>(url: string, meta?: MetaConfig<false>): ResponseData<R> function post<R = any>(url: string, meta?: MetaConfig): R | ResponseData<R> { return {} as R | ResponseData<R> } function login1(meta: MetaConfig = { clean: false }) { return post<Login>('/login', meta) } function login2(meta: MetaConfig = { clean: true }) { return post<Login>('/login', meta) } // 疑问:为何`res`的类型与期望不符? const res1 = login1() // 期望类型为`ResponseData<Login>` const res2 = login2() // 期望类型为`Login` const res3 = login1({ clean: false }) // 期望类型为`ResponseData<Login>` const res4 = login1({ clean: true }) // 期望类型为`Login`
一、类型不符的原因
login1和login2的参数meta被声明为MetaConfig(等价于MetaConfig<boolean>),而非带有具体布尔值约束的MetaConfig<true>或MetaConfig<false>。TypeScript会根据参数的静态类型匹配函数重载,而非运行时的值。- 由于
MetaConfig的泛型默认值是boolean,meta的静态类型是MetaConfig<boolean>,此时TypeScript会优先匹配第一个重载(因为MetaConfig<boolean>兼容MetaConfig<true>的结构),导致返回类型被推断为Login而非期望的ResponseData<Login>。 - 参数的默认值不会改变其静态类型声明,所以即使你设置了
{ clean: false }作为默认值,TypeScript仍然会按MetaConfig<boolean>处理。
二、实现动态返回类型的正确方式
方案1:保留重载,用泛型约束参数类型
通过给login1和login2添加泛型参数,让meta的clean值类型被精准推断,从而匹配post的正确重载:
interface MetaConfig<C extends boolean = boolean> { clean?: C } interface ResponseData<T> { code: number data: T message: string } interface Login { info: any } function post<R = any>(url: string, meta?: MetaConfig<true>): R function post<R = any>(url: string, meta?: MetaConfig<false>): ResponseData<R> function post<R = any>(url: string, meta?: MetaConfig): R | ResponseData<R> { return {} as R | ResponseData<R> } // 用泛型C约束meta的clean类型,同时指定默认泛型值 function login1<C extends boolean = false>(meta: MetaConfig<C> = { clean: false } as MetaConfig<C>) { return post<Login>('/login', meta) } function login2<C extends boolean = true>(meta: MetaConfig<C> = { clean: true } as MetaConfig<C>) { return post<Login>('/login', meta) } // 现在类型推断符合预期: const res1 = login1() // ResponseData<Login> const res2 = login2() // Login const res3 = login1({ clean: false }) // ResponseData<Login> const res4 = login1({ clean: true }) // Login
关键改动说明:
- 泛型参数
C将meta的类型与clean的具体布尔值绑定 - 默认值通过
as MetaConfig<C>断言类型,确保TypeScript关联默认值与泛型C - 传入不同
clean值时,C会被推断为true或false,post函数能匹配正确的重载
方案2:用条件类型替代重载
直接通过条件类型根据泛型C的类型决定返回值,逻辑更简洁:
interface MetaConfig<C extends boolean = boolean> { clean?: C } interface ResponseData<T> { code: number data: T message: string } interface Login { info: any } // 用条件类型直接映射返回值类型 function post<R = any, C extends boolean = false>(url: string, meta?: MetaConfig<C>): C extends true ? R : ResponseData<R> { return {} as any } function login1<C extends boolean = false>(meta: MetaConfig<C> = { clean: false } as MetaConfig<C>) { return post<Login, C>('/login', meta) } function login2<C extends boolean = true>(meta: MetaConfig<C> = { clean: true } as MetaConfig<C>) { return post<Login, C>('/login', meta) } // 类型推断同样符合预期: const res1 = login1() // ResponseData<Login> const res2 = login2() // Login const res3 = login1({ clean: false }) // ResponseData<Login> const res4 = login1({ clean: true }) // Login
内容的提问来源于stack exchange,提问作者linka
相关产品推荐
相关产品推荐

