You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 13:07:05