阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 泛型与继承

泛型与继承

作者:陈川 阅读数:42681人阅读 分类: TypeScript

泛型的基本概念

泛型是TypeScript中用于创建可复用组件的核心工具。它允许在定义函数、接口或类时不预先指定具体类型,而是在使用时再指定。泛型的核心思想是参数化类型,即把类型当作参数传递。

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

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

泛型通过<T>语法声明类型参数,这个T可以在函数体中使用。调用时可以显式指定类型,也可以让TypeScript自动推断类型。泛型的主要优势在于保持类型安全的同时提供了代码复用性。

继承的基本概念

继承是面向对象编程的重要特性,TypeScript通过extends关键字实现类继承。子类可以继承父类的属性和方法,同时可以添加自己的特性或覆盖父类行为。

class Animal {
    name: string;
    
    constructor(name: string) {
        this.name = name;
    }
    
    move(distance: number = 0) {
        console.log(`${this.name} moved ${distance}m`);
    }
}

class Dog extends Animal {
    bark() {
        console.log("Woof! Woof!");
    }
}

const dog = new Dog("Buddy");
dog.bark();  // Woof! Woof!
dog.move(10); // Buddy moved 10m

泛型与继承的结合

当泛型遇到继承时,可以创建出更灵活的类型系统。泛型类可以继承自非泛型类,非泛型类也可以继承自泛型类,甚至泛型类之间也可以相互继承。

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

class StringNumber extends GenericNumber<string> {
    constructor() {
        super();
        this.zeroValue = "";
        this.add = (x, y) => x + y;
    }
}

const stringNum = new StringNumber();
console.log(stringNum.add("Hello", "World")); // HelloWorld

泛型约束与继承

通过extends关键字可以对泛型参数进行约束,限制它必须符合某种类型。这在需要访问特定属性或方法时特别有用。

interface Lengthwise {
    length: number;
}

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

loggingIdentity("hello"); // 5
loggingIdentity([1, 2, 3]); // 3
loggingIdentity({length: 10, value: "test"}); // 10

泛型类继承中的类型参数

泛型类继承时,子类可以保留父类的类型参数,也可以固定部分或全部类型参数。

class Base<T, U> {
    constructor(public prop1: T, public prop2: U) {}
}

class Derived1<V> extends Base<string, V> {
    constructor(prop2: V) {
        super("default", prop2);
    }
}

class Derived2 extends Base<number, boolean> {
    constructor(prop1: number, prop2: boolean) {
        super(prop1, prop2);
    }
}

方法重写与泛型

子类可以重写父类的泛型方法,但必须保持兼容的签名。返回类型可以是父类方法返回类型的子类型。

class Parent {
    process<T>(input: T): T[] {
        return [input];
    }
}

class Child extends Parent {
    process<T extends string>(input: T): string[] {
        return [input.toUpperCase()];
    }
}

const child = new Child();
console.log(child.process("hello")); // ["HELLO"]

泛型接口继承

接口也可以使用泛型,并且支持继承。泛型接口可以继承非泛型接口,反之亦然。

interface NotGeneric {
    id: number;
}

interface Generic<T> extends NotGeneric {
    value: T;
}

interface Pair<T, U> {
    first: T;
    second: U;
}

interface NumberPair extends Pair<number, number> {
    sum(): number;
}

const pair: NumberPair = {
    first: 1,
    second: 2,
    sum() { return this.first + this.second; }
};

条件类型与继承

TypeScript 2.8引入的条件类型,结合extends关键字,可以根据类型关系选择不同的类型。

type IsString<T> = T extends string ? "yes" : "no";

type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"

type ExtractString<T> = T extends string ? T : never;
type C = ExtractString<"hello" | 42 | true>; // "hello"

泛型与类静态成员

静态成员不能直接使用类的类型参数,但可以通过泛型函数或静态泛型方法实现类似功能。

class GenericClass<T> {
    static defaultValue: any; // 不能是T
    
    static create<U>(value: U): GenericClass<U> {
        const instance = new GenericClass<U>();
        // 初始化逻辑
        return instance;
    }
}

const instance = GenericClass.create<string>("test");

高级模式:混入与泛型

混入模式结合泛型可以创建高度可复用的组件。通过泛型约束确保混入类具有所需的结构。

type Constructor<T = {}> = new (...args: any[]) => T;

function Timestamped<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        timestamp = Date.now();
    };
}

class User {
    name: string;
    
    constructor(name: string) {
        this.name = name;
    }
}

const TimestampedUser = Timestamped(User);
const user = new TimestampedUser("Alice");
console.log(user.timestamp); // 当前时间戳

类型推断与泛型继承

TypeScript在泛型继承场景下的类型推断能力非常强大,能够自动推导出复杂的类型关系。

class Box<T> {
    value: T;
    
    constructor(value: T) {
        this.value = value;
    }
    
    map<U>(f: (x: T) => U): Box<U> {
        return new Box(f(this.value));
    }
}

class ExtendedBox<T> extends Box<T> {
    log(): void {
        console.log(this.value);
    }
}

const box = new ExtendedBox(42);
box.log(); // 42
const stringBox = box.map(x => x.toString());
stringBox.log(); // "42"

泛型参数默认值

类似于函数参数默认值,泛型类型参数也可以指定默认类型。

interface PaginatedResponse<T = any> {
    data: T[];
    total: number;
    page: number;
}

const userResponse: PaginatedResponse<{name: string}> = {
    data: [{name: "Alice"}],
    total: 1,
    page: 1
};

const anyResponse: PaginatedResponse = {
    data: [1, 2, 3],
    total: 3,
    page: 1
};

泛型与装饰器

装饰器可以应用于泛型类,但需要注意类型信息的处理方式。

function logClass<T extends {new(...args: any[]): {}}>(constructor: T) {
    return class extends constructor {
        constructor(...args: any[]) {
            super(...args);
            console.log(`Instance created: ${constructor.name}`);
        }
    };
}

@logClass
class GenericEntity<T> {
    constructor(public value: T) {}
}

const entity = new GenericEntity<string>("test"); // 输出: Instance created: GenericEntity

泛型与索引类型

结合索引类型和泛型可以创建灵活的类型操作工具。

function pluck<T, K extends keyof T>(objs: T[], key: K): T[K][] {
    return objs.map(obj => obj[key]);
}

const people = [
    {name: "Alice", age: 30},
    {name: "Bob", age: 25}
];

const names = pluck(people, "name"); // string[]
const ages = pluck(people, "age"); // number[]

泛型与递归类型

泛型可以用于定义递归类型结构,这在处理树形数据时特别有用。

type TreeNode<T> = {
    value: T;
    left?: TreeNode<T>;
    right?: TreeNode<T>;
};

const numberTree: TreeNode<number> = {
    value: 1,
    left: {
        value: 2,
        left: {value: 4}
    },
    right: {
        value: 3
    }
};

function traverse<T>(node: TreeNode<T>, visit: (value: T) => void) {
    visit(node.value);
    if (node.left) traverse(node.left, visit);
    if (node.right) traverse(node.right, visit);
}

traverse(numberTree, console.log); // 依次输出 1, 2, 4, 3

泛型与Promise

Promise天然就是泛型的,可以表示异步操作的最终结果类型。

function fetchData<T>(url: string): Promise<T> {
    return fetch(url).then(response => response.json());
}

interface User {
    id: number;
    name: string;
}

fetchData<User[]>("/api/users")
    .then(users => {
        users.forEach(user => console.log(user.name));
    });

泛型与React组件

在React中使用TypeScript时,泛型可以用于创建可复用的组件。

interface ListProps<T> {
    items: T[];
    renderItem: (item: T) => React.ReactNode;
}

function List<T>({items, renderItem}: ListProps<T>) {
    return (
        <ul>
            {items.map((item, index) => (
                <li key={index}>{renderItem(item)}</li>
            ))}
        </ul>
    );
}

const users = [{id: 1, name: "Alice"}, {id: 2, name: "Bob"}];

<UserList users={users} />;

function UserList({users}: {users: Array<{id: number, name: string}>}) {
    return (
        <List
            items={users}
            renderItem={user => <span>{user.name}</span>}
        />
    );
}

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

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

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌