TypeScript 中的函数重载与使用联合类型

Posted

技术标签:

【中文标题】TypeScript 中的函数重载与使用联合类型【英文标题】:Function Overloading in TypeScript vs Using a Union Type 【发布时间】:2021-02-20 18:11:41 【问题描述】:

我正在阅读有关函数重载的信息。本质上它们是什么,假设您正在创建 3 个具有相同名称的函数,它们传递 3 个不同的参数和返回类型。我是 TS 的新手,我想知道以下问题:传递联合类型和返回联合类型不会相同吗?还是完全不同?

这就是我脑海中出现的例子。这行不行?

重载:

function f1(a: string) 

function f1(a: number) 

使用联合类型:

function f1(a: string | number):string | number  


【问题讨论】:

函数重载是 TS 的纯编译时构造,因此您的双 f1 函数声明将触发错误(JS 采用最后定义的函数,覆盖前一个)。 你是什么意思“这会工作”?第一个代码块是一个错误,因为您似乎两次实现了相同的功能。如果您想要重载,则需要多个调用签名和最多一个实现。重载通常表示输入和输出类型之间的关系。你的f1 似乎没有返回任何东西,所以我不知道该说什么,但大概一个返回string,另一个返回number。也许更多地充实这个想法,并有一些适合放入像The TypeScript Playground这样的IDE作为minimal reproducible example。 【参考方案1】:

函数重载将特定的输入类型映射到特定的返回类型。使用联合,您只知道返回是有效类型之一,但您失去了输入和输出之间的关联。这可能是也可能不是问题,具体取决于您使用该功能的方式和位置。但这就是区别。

这是重载的样子。重载签名中的最后一行不是重载之一,它描述了实现的参数。有时你会看到any,但你也可以在这里使用联合。

function overloaded(a: string): string
function overloaded(a: number): number
function overloaded(a: any): any 
    return a;

不同的参数根据它们匹配的重载返回特定类型。

const oNum: number = overloaded(0);
const oStr: string = overloaded("");
const oBool = overloaded(true); //error

在我们的联合中,两种输入类型都只返回联合,所以我们失去了特异性。

function union(a: string | number): string | number 
    return a;

const uNum: string | number = union(0);
const uStr: string | number = union("");
const uBool = union(true); //error

还有第三个选项是typescript generics。这允许我们在接受无限多种类型的同时保持特异性。 boolean 示例现在可以使用。我们告诉 typescript “查看参数的类型 a 并称之为 T”。然后我们得到这个类型变量T,我们可以在返回类型中使用它。这里我们只是直接返回相同类型的T,但是你可以用它做很多事情。

function generic<T>(a: T): T 
    return a;

const gNum: number = generic(0);
const gStr: string = generic("");
const gBool: boolean = generic(true);

Typescript Playground Link

【讨论】:

【参考方案2】:

对于函数参数类型,函数重载可以比联合类型和泛型类型更具体。

现实世界的例子:

createActivity 函数用于创建具有不同输入参数的三种类型的活动。 CreateRenewalActivityParamsCreateInsureActivityParams CreateTrafficGenerationActivityParams

interface CreateActivityParams 
  activityCode: string;
  activityName: string;
  activityDesc: string;
  activityType: number;
  activityTypeName: string;
  startTime: string;
  endTime: string;
  paymentType: string | null;

interface CreateRenewalActivityParams extends CreateActivityParams 
  installmentCases: any[];


interface CreateInsureActivityParams extends CreateActivityParams 
  cases: any[];


interface CreateTrafficGenerationActivityParams extends CreateActivityParams 
  cases: any[];

export async function createActivity(data: CreateTrafficGenerationActivityParams): Promise<boolean>;
export async function createActivity(data: CreateInsureActivityParams): Promise<boolean>;
export async function createActivity(data: CreateRenewalActivityParams): Promise<boolean>;
export async function createActivity(data: CreateActivityParams): Promise<boolean> 
  return true;


// TSC throw error
createActivity(
  activityCode: 'test code',
  activityName: 'test name',
  activityDesc: 'test desc',
  activityType: 8,
  activityTypeName: 'test type name',
  startTime: '2021-07-20 00:00:00',
  endTime: '2025-07-20 23:59:59',
  paymentType: null,
  cases: [
    
      pictureCode: 'test pic code',
      authDesc: null,
    ,
    
      pictureCode: 'test pic code',
      authDesc: null,
    ,
  ],
  installmentCases: [],
);


export async function createActivityUnion(data: CreateTrafficGenerationActivityParams | CreateInsureActivityParams | CreateRenewalActivityParams) 
    return true;


// TSC pass
createActivityUnion(
  activityCode: 'test code',
  activityName: 'test name',
  activityDesc: 'test desc',
  activityType: 8,
  activityTypeName: 'test type name',
  startTime: '2021-07-20 00:00:00',
  endTime: '2025-07-20 23:59:59',
  paymentType: null,
  cases: [
    
      pictureCode: 'test pic code',
      authDesc: null,
    ,
    
      pictureCode: 'test pic code',
      authDesc: null,
    ,
  ],
  installmentCases: [],
);

export async function createActivityGeneric<Data extends CreateActivityParams>(data: Data) 
    return true;


// TSC pass
createActivityGeneric(
  activityCode: 'test code',
  activityName: 'test name',
  activityDesc: 'test desc',
  activityType: 8,
  activityTypeName: 'test type name',
  startTime: '2021-07-20 00:00:00',
  endTime: '2025-07-20 23:59:59',
  paymentType: null,
  cases: [
    
      pictureCode: 'test pic code',
      authDesc: null,
    ,
    
      pictureCode: 'test pic code',
      authDesc: null,
    ,
  ],
  installmentCases: [],
)

现在,我们希望函数的参数在同时包含 installmentCasescases 字段时报告类型不兼容错误,因为它们属于不同的活动类型,这可以使用函数重载来完成。

我们可以用上述三种参数类型为createActivity函数编写三个更具体的重载签名和一个CreateActivityParams类型的通用实现签名。

TypeScript Playground

【讨论】:

【参考方案3】:

只是扩展 @Linda Paiste 关于泛型类型的评论

const gNum: number = generic(0);

这基本上是在说

function generic<0>(a: 0): 0;

这有时可能不是您想要的。所以在使用泛型时,你要传入一个类型参数:

const gNum: number = generic<number>(0);

【讨论】:

以上是关于TypeScript 中的函数重载与使用联合类型的主要内容,如果未能解决你的问题,请参考以下文章

如何使用泛型缩小 TypeScript 联合类型

Typescript泛型函数重载

为啥 Typescript 不以正确的方式支持函数重载?

TypeScript——泛型

在 onClick 道具中使用联合类型

TypeScript 类方法具有与构造函数相同的重载签名