TypeScript 类的类型 —— 作为类型使用

本文献给:

已掌握 TypeScript 类基础、继承、静态成员等知识的开发者。本文将带你理解类在 TypeScript 中的双重身份(值和类型),学习如何将类作为类型注解、获取构造函数类型,以及类类型接口的常见用法。

你将学到:

  1. 类同时创建值和类型的原理
  2. 将类作为类型注解使用
  3. typeof MyClass 获取构造函数类型
  4. 类类型与接口的互换
  5. 实际场景:依赖注入、工厂函数

目录

一、类的双重身份

在 TypeScript 中,声明一个类会创建两个东西

  • :构造函数(运行时存在)
  • 类型:实例的类型(编译时存在)
typescript 复制代码
class Point {
    x: number = 0;
    y: number = 0;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
    distance(): number {
        return Math.hypot(this.x, this.y);
    }
}

// Point 作为类型(实例类型)
const p1: Point = new Point(3, 4);

// Point 作为值(构造函数)
const PointClass = Point;       // 将构造函数赋给变量
const p2 = new PointClass(1, 2);

这种双重身份是 TypeScript 特有的,因为 JavaScript 类本身就是构造函数(值),而 TypeScript 额外添加了类型信息。

二、类作为类型注解

类名可以直接用作类型,表示该类的实例。

typescript 复制代码
class User {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    greet() {
        return `Hello, ${this.name}`;
    }
}

function printUser(user: User) {
    console.log(user.greet());
}

const alice = new User("Alice");
printUser(alice);  // OK

// 对象字面量只要结构匹配也可以
const bob = { name: "Bob", greet: () => "Hi Bob" };
printUser(bob);    // OK(结构化类型兼容)

注意:TypeScript 是结构化类型系统,只要对象形状匹配,即使不是通过 new User 创建,也可以赋值给 User 类型。

2.1 类与接口的互换

类和接口在类型层面可以互相兼容,因为接口只描述形状,类也描述形状。

typescript 复制代码
interface Person {
    name: string;
    age: number;
}

class Student {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

const s: Person = new Student("Alice", 20);  // OK,类实例可赋给接口类型

三、获取构造函数类型:typeof MyClass

有时我们需要表示构造函数本身的类型(而不是实例类型)。使用 typeof 类名可以获取构造函数类型。

typescript 复制代码
class Point {
    constructor(public x: number, public y: number) {}
}

type PointConstructor = typeof Point;
// 等价于: new (x: number, y: number) => Point

const ctor: PointConstructor = Point;
const p = new ctor(10, 20);  // p 类型为 Point

构造函数类型可以用于参数类型,表示需要接收一个类的构造函数。

typescript 复制代码
function createInstance<T>(ctor: new (...args: any[]) => T, ...args: any[]): T {
    return new ctor(...args);
}

class Animal {
    constructor(public name: string) {}
}

const animal = createInstance(Animal, "Dog");
console.log(animal.name);  // "Dog"

3.1 更精确的构造函数类型

可以显式写出构造签名的类型。

typescript 复制代码
interface PointConstructor {
    new (x: number, y: number): Point;
}

function makePoint(ctor: PointConstructor, x: number, y: number): Point {
    return new ctor(x, y);
}

3.2 类中的静态部分与实例部分

typeof MyClass 得到的类型包含了类的所有静态成员和构造函数,而实例类型只包含实例成员。

typescript 复制代码
class Service {
    static version = "1.0";
    name: string = "service";
    static getVersion() {
        return this.version;
    }
    getName() {
        return this.name;
    }
}

type ServiceInstance = Service;           // 实例类型:{ name: string; getName(): string }
type ServiceStatic = typeof Service;      // 静态类型:{ version: string; getVersion(): string; new(): Service }

const s: ServiceInstance = new Service();
const ctor: ServiceStatic = Service;
console.log(ctor.version);  // "1.0"

四、类类型与工厂模式

利用构造函数类型可以实现类型安全的工厂函数。

typescript 复制代码
interface Product {
    getPrice(): number;
}

class Book implements Product {
    constructor(public title: string, private price: number) {}
    getPrice() { return this.price; }
}

class Electronic implements Product {
    constructor(public name: string, private price: number) {}
    getPrice() { return this.price; }
}

class ProductFactory {
    static create<T extends Product>(
        ctor: new (...args: any[]) => T,
        ...args: any[]
    ): T {
        return new ctor(...args);
    }
}

const book = ProductFactory.create(Book, "TypeScript Guide", 29.99);
const laptop = ProductFactory.create(Electronic, "Laptop", 999);

console.log(book.getPrice());
console.log(laptop.getPrice());

五、类作为接口使用

因为类定义了一个形状,你可以用类来约束其他对象(类似于接口)。这在某些场景下可以减少重复定义。

typescript 复制代码
class Config {
    url: string = "";
    timeout: number = 5000;
    retries: number = 3;
}

// 不需要单独定义接口
function init(config: Config) {
    console.log(config.url);
}

// 传入普通对象,只要形状匹配即可
init({ url: "https://api.com", timeout: 3000, retries: 2 });

但通常建议:如果不需要构造函数和具体实现,专门定义接口更清晰。

六、常见错误与注意事项

6.1 混淆实例类型与构造函数类型

typescript 复制代码
class Foo {}
let a: Foo = Foo;      // ❌ Foo 是构造函数,不能赋值给实例类型
let b: typeof Foo = Foo; // ✅

6.2 使用 this 作为返回类型

类方法可以返回 this 类型,表示返回当前实例(支持链式调用,且在子类中自动适配)。

typescript 复制代码
class Animal {
    name: string = "";
    setName(name: string): this {
        this.name = name;
        return this;
    }
}

class Dog extends Animal {
    bark(): this {
        console.log(`${this.name} barks`);
        return this;
    }
}

const dog = new Dog().setName("Buddy").bark();  // 链式调用,类型正确

6.3 类与接口的兼容性陷阱

由于结构化类型系统,即使类中有私有字段,也不会阻止类型兼容(但私有字段在目标类型中必须也存在)。

typescript 复制代码
class A {
    private x = 1;
}
class B {
    private x = 1;
}
const a: A = new B(); // ❌ 私有成员不兼容

6.4 泛型类中的静态成员

如之前所说,静态成员不能引用类的泛型参数。同样,typeof 在泛型类中也需要小心。

typescript 复制代码
class Box<T> {
    value: T;
    constructor(value: T) { this.value = value; }
}
// type BoxCtor<T> = typeof Box<T>; // ❌ 不能这样写

可以使用泛型函数来传递构造函数。

七、综合示例

typescript 复制代码
// 定义一个模型基类
abstract class Model {
    id: number = 0;
    abstract tableName(): string;
    save() {
        console.log(`Saving to ${this.tableName()}`);
        return this;
    }
}

// 用户模型
class User extends Model {
    name: string = "";
    email: string = "";
    tableName() { return "users"; }
}

// 订单模型
class Order extends Model {
    total: number = 0;
    tableName() { return "orders"; }
}

// 仓库,接收构造函数类型,可以创建和存储模型实例
class Repository<T extends Model> {
    private items: T[] = [];
    constructor(private ctor: new () => T) {}
    
    create(): T {
        const instance = new this.ctor();
        instance.id = this.items.length + 1;
        this.items.push(instance);
        return instance;
    }
    
    findAll(): T[] {
        return this.items;
    }
}

// 使用
const userRepo = new Repository(User);
const user1 = userRepo.create();
user1.name = "Alice";
user1.save();

const orderRepo = new Repository(Order);
const order1 = orderRepo.create();
order1.total = 299;
order1.save();

// 获取构造函数类型示例
type UserConstructor = typeof User;
const Ctor: UserConstructor = User;
const user2 = new Ctor();
user2.name = "Bob";
console.log(user2.name);

// 类作为类型注解
function printModel(m: Model) {
    console.log(`Model id: ${m.id}, table: ${m.tableName()}`);
}
printModel(user1);
printModel(order1);

八、小结

概念 语法/示例 说明
类作为类型 let p: MyClass 表示实例类型
类作为值 const C = MyClass 构造函数值
构造函数类型 typeof MyClass 包含构造签名和静态成员
实例类型 vs 静态类型 MyClass vs typeof MyClass 前者是实例形状,后者是构造函数
构造签名 new (x: number) => T 描述可构造的函数
工厂函数参数 (ctor: new () => T) => T 传入构造函数,返回实例
this 类型 method(): this { return this; } 支持链式调用,子类自动适配

觉得文章有帮助?别忘了:

👍 点赞 👍 -- 给我一点鼓励

⭐ 收藏 ⭐ -- 方便以后查看

🔔 关注 🔔 -- 获取更新通知


标签: #TypeScript #类的类型 #构造函数 #typeof #学习笔记 #前端开发

相关推荐
之歆2 小时前
Day16_JavaScript 轮播图与事件工程实战(下篇)
服务器·开发语言·前端·javascript·网络·性能优化
kyriewen3 小时前
我关掉了Copilot:因为我写的代码出现在了别人的建议里
前端·javascript·ai编程
SmartRadio4 小时前
STM32WLE5 LoRa Smart TDMA 完整协议栈实现(工程级可直接编译)-【1】
javascript·stm32·单片机·嵌入式硬件·lora·自组网·smart tdma
❀搜不到4 小时前
Ubuntu查看指定Python程序的CPU、GPU、内存占用情况
linux·python·ubuntu
竹林8184 小时前
用 wagmi v2 踩坑两天,我终于搞懂了多链钱包切换
前端·javascript
子云zy5 小时前
JS 对象与包装类:new 做了什么?字符串为什么有 length?
前端·javascript
阿隅5 小时前
TS 深度解析:同为 ? 可选语法,为什么赋值一错一对?类类型与this绑定底层拆解
typescript
茶底世界之下6 小时前
你的 Mac 里,藏着一支 AI 开发团队
前端·javascript
小白学大数据6 小时前
Playwright 爬虫:Python 爬取 JS 渲染的 JSP 网站
开发语言·javascript·爬虫·python·数据分析