TypeScript中如何依托ready状态自动窄化可选参数类型?
问题解决:自动窄化useRequest回调中的参数类型
原代码与报错
// 第三方库 type AnyFn = (...args: any[]) => any; type UseRequestOptions = { ready?: boolean; } const useRequest = (fn: AnyFn, options: UseRequestOptions) => { const ready = options.ready ?? true; if (ready) { fn() } } // 业务代码 const api = { getUser(id: number) { } } const useGetUserById = (id: number | undefined) => useRequest(() => api.getUser(id), { ready: !!id }) const id: number | undefined = 1; useGetUserById(1)
编译时TypeScript抛出错误:
Argument of type 'number | undefined' is not assignable to parameter of type 'number'.
Type 'undefined' is not assignable to type 'number'.(2345)
需求:不使用类型断言(如id as number),通过修改第三方库或用户侧代码,让api.getUser接收的id自动窄化为number类型。
解决方案
方案1:修改第三方库的类型定义(最优雅)
原库的AnyFn类型太宽泛,无法关联ready状态和回调函数的参数合法性。可以重构类型,让ready和回调参数建立依赖:
// 修改后的第三方库代码 type UseRequestOptions<T extends unknown[] = []> = { ready?: boolean; args?: T; // 存储回调所需的参数,仅在ready为true时传递 } const useRequest = <T extends unknown[], R>( fn: (...args: T) => R, options: UseRequestOptions<T> ) => { const ready = options.ready ?? true; if (ready) { // 断言参数存在:因为ready为true时,业务侧会确保参数合法 fn(...(options.args as T)); } }
业务侧使用时,TypeScript会自动推导参数类型:
const useGetUserById = (id: number | undefined) => useRequest(api.getUser, { ready: !!id, args: id ? [id] : undefined // 仅当id存在时传参 })
此时TypeScript能识别:当ready为true时,args必然是[number],不会触发类型错误。
方案2:用户侧不修改库,利用控制流分析
在回调函数内部加类型守卫,让TypeScript自动窄化id的类型:
const useGetUserById = (id: number | undefined) => useRequest(() => { // 控制流分析自动将id窄化为number if (id) api.getUser(id); }, { ready: !!id })
因为ready为true的条件是!!id,只有id存在时才会执行回调,所以if (id)分支必然会命中,不会有运行时问题,编译也能通过。
方案3:用函数重载约束入参
给useGetUserById加重载,明确不同入参类型的处理逻辑:
// 重载定义 function useGetUserById(id: number): void; function useGetUserById(id: undefined): void; // 实现 function useGetUserById(id: number | undefined) { return useRequest(() => { if (id) api.getUser(id); }, { ready: !!id }); }
这样用户调用时,TypeScript会根据入参类型做正确推断,同时回调内部的id在执行时必然是number。
内容的提问来源于stack exchange,提问作者Lin Du
相关产品推荐
相关产品推荐

