阿里云主机折上折
  • 微信号
Current Site:Index > Generics and function overloading

Generics and function overloading

Author:Chuan Chen 阅读数:13263人阅读 分类: TypeScript

Basic Concepts of Generics

Generics are a core tool in TypeScript for creating reusable components. They allow developers to write code that can handle multiple types without repeating logic for each type. The essence of generics is type parameterization, where functions, interfaces, or classes are defined without specifying concrete types in advance, but instead specify them when used.

function identity<T>(arg: T): T {
    return arg;
}

let output1 = identity<string>("hello");
let output2 = identity<number>(42);

In this example, T is a type variable that captures the type passed by the user. When calling, the type parameter can be explicitly specified or inferred by TypeScript:

let output3 = identity("world");  // Inferred as string
let output4 = identity(100);     // Inferred as number

Generic Constraints

Sometimes we need to restrict the range of generic parameter types. In such cases, the extends keyword can be used to add constraints:

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

loggingIdentity("hello");  // Valid, strings have a length property
loggingIdentity(3);       // Error, numbers lack a length property

Multiple type parameters can also be combined:

function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}

let x = { a: 1, b: 2 };
getProperty(x, "a");  // Valid
getProperty(x, "c");  // Error, "c" is not a property of x

Generic Interfaces and Classes

Generics can also be applied to interfaces and classes:

interface GenericIdentityFn<T> {
    (arg: T): T;
}

let myIdentity: GenericIdentityFn<number> = identity;

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = (x, y) => x + y;

Basics of Function Overloading

Function overloading allows a function to accept different types or numbers of arguments and return different types of results. In TypeScript, function overloading is implemented by providing multiple function signatures:

function reverse(value: string): string;
function reverse(value: number): number;
function reverse(value: string | number): string | number {
    if (typeof value === "string") {
        return value.split("").reverse().join("");
    }
    return Number(value.toString().split("").reverse().join(""));
}

reverse("hello");  // Returns "olleh"
reverse(123);      // Returns 321

Combining Overloading with Generics

Generics and function overloading can be combined to create more flexible type-safe functions:

function combine<T>(a: T, b: T): T;
function combine<T, U>(a: T, b: U): [T, U];
function combine(a: any, b: any) {
    if (typeof a === typeof b) {
        return a + b;
    }
    return [a, b];
}

combine(1, 2);       // Returns 3
combine("a", "b");   // Returns "ab"
combine(1, "a");     // Returns [1, "a"]

Advanced Overloading Patterns

For more complex scenarios, conditional types and mapped types can be used to enhance overloading:

type OverloadedReturn<T> = 
    T extends string ? number :
    T extends number ? string :
    never;

function transform<T extends string | number>(input: T): OverloadedReturn<T>;
function transform(input: any) {
    if (typeof input === "string") {
        return input.length;
    }
    return input.toString();
}

const strLength = transform("hello");  // Type number
const numStr = transform(42);         // Type string

Practical Application Scenarios

Generics and overloading are particularly useful when handling API responses:

interface ApiResponse<T> {
    data: T;
    status: number;
}

function fetchData<T>(url: string): Promise<ApiResponse<T>>;
function fetchData<T>(url: string, callback: (data: T) => void): void;
function fetchData<T>(url: string, callback?: (data: T) => void): Promise<ApiResponse<T>> | void {
    const promise = fetch(url).then(res => res.json());
    if (callback) {
        promise.then(data => callback(data));
        return;
    }
    return promise;
}

// Usage 1: Promise-based
fetchData<User>("/api/user").then(response => {
    console.log(response.data);
});

// Usage 2: Callback-based
fetchData<Product>("/api/product", product => {
    console.log(product.price);
});

Performance Considerations

While generics and overloading provide strong type safety, the impact on compile-time performance should also be considered. Complex generic types and multiple overloads can increase type-checking time. For performance-sensitive projects, consider:

  1. Avoiding overly nested generic types
  2. Limiting the number of overload signatures (typically no more than 5-7)
  3. Using simpler type annotations in hot-path code
// Complex generics that may impact performance
type DeepNested<T> = {
    [K in keyof T]: T[K] extends object ? DeepNested<T[K]> : T[K];
};

// Simpler alternative
interface Simplified {
    id: string;
    name: string;
    metadata?: Record<string, any>;
}

Boundaries of Type Inference

TypeScript's type inference has clear boundaries when handling generics and overloading. Understanding these limitations helps in writing more robust code:

function firstElement<T>(arr: T[]): T {
    return arr[0];
}

// Automatically inferred as unknown[]
const result = firstElement([]);  

// Explicit type parameter needed
const betterResult = firstElement<string>([]); 

For overloaded functions, TypeScript tries each overload signature in order until a match is found:

function createDate(timestamp: number): Date;
function createDate(year: number, month: number, day: number): Date;
function createDate(overload1: number, overload2?: number, overload3?: number): Date {
    if (overload2 !== undefined && overload3 !== undefined) {
        return new Date(overload1, overload2, overload3);
    }
    return new Date(overload1);
}

createDate(1640995200000);          // Uses the first overload
createDate(2022, 0, 1);            // Uses the second overload

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:泛型类型推断

下一篇:泛型与继承

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.