TypeScript编译器未正确窄化函数返回类型的问题求助
你遇到的这个问题挺常见的——泛型函数里的条件类型虽然能在调用端正确推断返回类型,但在函数实现内部,TypeScript的控制流分析没法把if (!input)这个判断和泛型T的条件约束直接挂钩,所以它没法确认return null是符合返回类型的。
给你几个可行的解决办法,都能去掉@ts-ignore:
方法1:用类型断言成泛型T
既然当!input为真时,T的类型必然是null(因为T的约束是Pick<ABC, "a" | "b"> | null),那我们可以直接把null断言成T,这样编译器就会认可这个返回值的类型:
type ABC = { a: string; b: string | null; c: string; }; export const getwithABC = <T extends Pick<ABC, "a" | "b"> | null>( input: T ): T extends null ? null : (NonNullable<T> & { ab: string | null }) => { if (!input) { return null as T; // 替换@ts-ignore的安全断言 } const { a, b } = input; return { ...input, ab: b ? `${a}/${b}` : null, } as NonNullable<T> & { ab: string | null }; };
这个改动很小,而且完全安全——因为只有当input是null的时候才会进入这个分支,此时T的类型就是null,null as T完全符合类型约束。
方法2:改用函数重载
如果觉得泛型条件类型写起来有点绕,用函数重载会更直观,编译器在实现内部也能自动识别返回类型,不需要任何断言:
type ABC = { a: string; b: string | null; c: string; }; // 重载签名1:处理null输入 export function getwithABC(input: null): null; // 重载签名2:处理非空的Pick<ABC, "a" | "b">输入 export function getwithABC<T extends Pick<ABC, "a" | "b">>(input: T): T & { ab: string | null }; // 函数实现(类型兼容两个重载的宽松约束) export function getwithABC(input: Pick<ABC, "a" | "b"> | null) { if (!input) { return null; // 编译器直接认可这个返回类型 } const { a, b } = input; return { ...input, ab: b ? `${a}/${b}` : null, }; }
这种方式的优点是代码可读性更高,调用端的类型推断和你原来的代码完全一致,而且实现内部不需要任何类型断言。
补充说明
你原来的返回类型可以简化一下,多层条件类型T extends Pick<...> ? ... : T extends null ? null : never其实可以合并成T extends null ? null : (NonNullable<T> & { ab: string | null }),效果完全一样,代码更简洁。
另外,在返回非空对象的时候,如果你使用的是TS 4.9+版本且开启了strictNullChecks,编译器甚至能自动推断返回值的类型,不需要额外的类型断言。
备注:内容来源于stack exchange,提问作者rcbevans

