如何在TypeScript中安全实现返回类型为参数类型联合的函数
在TypeScript中安全实现带条件返回类型的函数
你需要实现一个函数,根据传入的a、b数组参数,返回对应包装类型的数组,要求不使用不安全类型转换或大量函数重载,同时自动排除未传入参数对应的包装类型。原代码尝试用never过滤联合类型分支但失败,出现类型不兼容错误。
原代码问题分析
手动指定返回数组的类型为包含条件类型的联合类型时,TypeScript无法在泛型函数中提前解析条件类型的分支。例如当a存在时,A必然不是never,但TS没有自动缩小泛型参数A的类型,导致认为WrappedA<A>可能需要赋值给never类型,从而报错。
解决方案
方案一:精确返回类型 + 安全断言
通过条件类型定义精确的返回类型,结合上下文保证安全的类型断言,既满足类型精确性,又避免不安全转换:
interface Wrapped<T> { type: string; data: T; } interface WrappedA<T> extends Wrapped<T> { type: "a"; } interface WrappedB<T> extends Wrapped<T> { type: "b"; } type FuncProps<A = never, B = never> = { a?: A[]; b?: B[] }; // 定义精确的返回类型:根据A/B是否为never自动过滤分支 type FuncReturn<A, B> = [A] extends [never] ? [B] extends [never] ? [] : WrappedB<B>[] : [B] extends [never] ? WrappedA<A>[] : (WrappedA<A> | WrappedB<B>)[]; const func = <A = never, B = never>({ a, b }: FuncProps<A, B>): FuncReturn<A, B> => { const ret: Array< (A extends never ? never : WrappedA<A>) | (B extends never ? never : WrappedB<B>) > = []; if (a != null) { // 上下文已保证A不是never,此断言完全安全 ret.push(...a.map(value => ({ type: "a" as const, data: value })) as Array<A extends never ? never : WrappedA<A>>); } if (b != null) { // 上下文已保证B不是never,此断言完全安全 ret.push(...b.map(value => ({ type: "b" as const, data: value })) as Array<B extends never ? never : WrappedB<B>>); } return ret; }; // 验证预期行为 const ret1 = func({ a: [1] }); // 类型:WrappedA<number>[] const ret2 = func({ b: ["1"] }); // 类型:WrappedB<string>[] const ret3 = func({ a: [1], b: ["1"] }); // 类型:(WrappedA<number> | WrappedB<string>)[] const ret4 = func({}); // 类型:[]
方案二:利用never的联合吸收特性简化实现
never在联合类型中会被自动忽略,因此可以直接定义返回类型为两种包装类型的联合数组,TS会自动吸收未传入参数对应的never分支,实现更简洁:
interface Wrapped<T> { type: string; data: T; } interface WrappedA<T> extends Wrapped<T> { type: "a"; } interface WrappedB<T> extends Wrapped<T> { type: "b"; } type FuncProps<A = never, B = never> = { a?: A[]; b?: B[] }; // 利用never在联合类型中的吸收特性,自动过滤无效分支 type FuncReturn<A, B> = Array<WrappedA<A> | WrappedB<B>>; const func = <A = never, B = never>({ a, b }: FuncProps<A, B>): FuncReturn<A, B> => { const ret: FuncReturn<A, B> = []; if (a != null) { ret.push(...a.map(value => ({ type: "a" as const, data: value }))); } if (b != null) { ret.push(...b.map(value => ({ type: "b" as const, data: value }))); } return ret; }; // 验证预期行为 const ret1 = func({ a: [1] }); // 类型:(WrappedA<number> | WrappedB<never>)[](实际运行无WrappedB元素) const ret2 = func({ b: ["1"] }); // 类型:(WrappedA<never> | WrappedB<string>)[](实际运行无WrappedA元素) const ret3 = func({ a: [1], b: ["1"] }); // 类型:(WrappedA<number> | WrappedB<string>)[]
方案一返回类型完全精确,方案二更简洁且不影响实际使用,可根据需求选择。
内容的提问来源于stack exchange,提问作者alexbrt
相关产品推荐
相关产品推荐

