阿里云主机折上折
  • 微信号
Current Site:Index > Instance types and class types

Instance types and class types

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

In TypeScript, the type system is one of its core features, and instance types and class types are key concepts for understanding the type system. The relationship and distinction between these two are crucial for writing type-safe code.

Definitions of Instance Type and Class Type

An instance type refers to the specific type of an object after it is instantiated from a class, while a class type refers to the type of the class itself. In TypeScript, a class is both a value and a type, but their meanings differ.

class Person {
  name: string;
  age: number;

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

// Class type
const PersonClass: typeof Person = Person;

// Instance type
const personInstance: Person = new Person("Alice", 30);

Use Cases for Class Types

Class types are typically used in scenarios where the class itself needs to be manipulated, such as factory functions or class decorators. The typeof operator can be used to obtain the type of a class.

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

const person = createInstance(Person, "Bob", 25);

Characteristics of Instance Types

Instance types include the instance properties and methods of a class but exclude static members. TypeScript automatically generates an instance type interface with the same name for a class declaration.

class Point {
  static origin = { x: 0, y: 0 };
  x: number;
  y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  distance() {
    return Math.sqrt(this.x ** 2 + this.y ** 2);
  }
}

// Instance types only include instance members
const p: Point = new Point(3, 4);
p.distance(); // 5
// p.origin; // Error: Instance types do not include static members

Class Types and Constructor Signatures

Class types are often represented as constructor signatures, describing how instances can be created using the new operator. This is particularly useful in generic constraints.

interface AnimalConstructor {
  new (name: string): Animal;
}

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

function createAnimal<T extends Animal>(ctor: new (name: string) => T, name: string): T {
  return new ctor(name);
}

const cat = createAnimal(Animal, "Mittens");

Class Expressions and Types

Class expressions can also produce types, which are used in the same way as class declarations.

const Rectangle = class {
  constructor(public width: number, public height: number) {}
  area() {
    return this.width * this.height;
  }
};

type RectangleType = InstanceType<typeof Rectangle>;
const rect: RectangleType = new Rectangle(10, 20);

Type Relationships in Generic Classes

Generic classes generate different instance types for each specific type parameter but share the same class type.

class Box<T> {
  value: T;
  constructor(value: T) {
    this.value = value;
  }
}

const numberBox: Box<number> = new Box(42);
const stringBox: Box<string> = new Box("hello");

// Class types are the same
const BoxClass: typeof Box = Box;

Types in Inheritance Relationships

The instance type of a subclass includes all members of the parent class, while the inheritance relationship of class types is more complex.

class Animal {
  move() {
    console.log("Moving");
  }
}

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

// Instance type compatibility
const animal: Animal = new Dog(); // Valid
// animal.bark(); // Error: Animal type does not have bark method

// Class type compatibility
const AnimalClass: typeof Animal = Animal;
// const DogClass: typeof Animal = Dog; // Valid

Private Fields and Types

Private fields affect type compatibility. Even if two classes have the same structure, their instance types are incompatible if the private fields come from different declarations.

class A {
  private x = 1;
  show() {
    console.log(this.x);
  }
}

class B {
  private x = 1;
  show() {
    console.log(this.x);
  }
}

const a: A = new A();
// const b: A = new B(); // Error: A and B are incompatible

Types of Abstract Classes

Abstract classes can be used as class types and can produce instance types, but abstract classes themselves cannot be instantiated.

abstract class Shape {
  abstract area(): number;
  display() {
    console.log(`Area: ${this.area()}`);
  }
}

// Class type
const ShapeClass: typeof Shape = Shape;

// Instance type
class Circle extends Shape {
  constructor(public radius: number) {
    super();
  }
  area() {
    return Math.PI * this.radius ** 2;
  }
}

const circle: Shape = new Circle(5);

Interfaces and Class Types

Interfaces can be used to describe the structure of class types, including static and instance members.

interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface;
}

class Clock implements ClockInterface {
  currentTime: Date;
  constructor(h: number, m: number) {
    this.currentTime = new Date(2023, 0, 1, h, m);
  }
  setTime(d: Date) {
    this.currentTime = d;
  }
}

function createClock(ctor: ClockConstructor, h: number, m: number): ClockInterface {
  return new ctor(h, m);
}

const clock = createClock(Clock, 12, 30);

Type Queries and Classes

The typeof operator has special behavior in the context of classes, allowing you to obtain the type of a class, including its static members.

class Logger {
  static level = "INFO";
  log(message: string) {
    console.log(`[${Logger.level}] ${message}`);
  }
}

type LoggerType = typeof Logger;
// Equivalent to
interface LoggerType {
  new (): Logger;
  level: string;
}

const loggerClass: LoggerType = Logger;
console.log(loggerClass.level); // "INFO"

Constructor Parameter Types

The ConstructorParameters utility type can be used to obtain the parameter types of a class constructor.

class User {
  constructor(public id: number, public name: string) {}
}

type UserParams = ConstructorParameters<typeof User>;
// Equivalent to [number, string]

function createUser(...args: UserParams): User {
  return new User(...args);
}

const user = createUser(1, "Alice");

Instance Type Utilities

TypeScript provides the InstanceType utility type to obtain the instance type of a class.

class Map<K, V> {
  private data = new Map<K, V>();
  set(key: K, value: V) {
    this.data.set(key, value);
  }
  get(key: K): V | undefined {
    return this.data.get(key);
  }
}

type MapInstance<K, V> = InstanceType<typeof Map<K, V>>;
const stringMap: MapInstance<string, number> = new Map<string, number>();
stringMap.set("age", 30);

Classes and Type Assertions

In some cases, type assertions may be needed to handle the relationship between class types and instance types.

class Base {
  baseMethod() {}
}

class Derived extends Base {
  derivedMethod() {}
}

const base: Base = new Derived();
// (base as Derived).derivedMethod(); // Requires type assertion

// Class type assertion
const DerivedClass = Derived as typeof Base;
const derived = new DerivedClass();

Types in Decorators

Class decorators receive the class type and can modify or extend the class's behavior.

function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return "Hello, " + this.greeting;
  }
}

Class Types and Mapped Types

Mapped types can be applied to class types to generate new types.

class Product {
  constructor(
    public id: number,
    public name: string,
    public price: number
  ) {}
}

type PartialProduct = Partial<InstanceType<typeof Product>>;
const partialProduct: PartialProduct = { name: "Laptop" };

Conditional Types and Classes

Conditional types can perform type judgments based on class types or instance types.

type IsNumber<T> = T extends number ? true : false;

class Numeric {
  value: number;
  constructor(v: number) {
    this.value = v;
  }
}

type Test1 = IsNumber<InstanceType<typeof Numeric>>; // false
type Test2 = IsNumber<Numeric["value"]>; // true

Class Types and Indexed Access

Indexed access types can be used to obtain the types of specific members within a class.

class Database {
  connection: { host: string; port: number };
  constructor(host: string, port: number) {
    this.connection = { host, port };
  }
}

type ConnectionType = Database["connection"];
// Equivalent to { host: string; port: number }

Class Types and Function Overloading

Class methods can be overloaded like regular functions, which affects the instance type.

class Overloaded {
  greet(name: string): string;
  greet(age: number): string;
  greet(value: string | number): string {
    if (typeof value === "string") {
      return `Hello, ${value}`;
    } else {
      return `You are ${value} years old`;
    }
  }
}

const overloaded = new Overloaded();
overloaded.greet("Alice"); // OK
overloaded.greet(30); // OK
// overloaded.greet(true); // Error

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

如果侵犯了你的权益请来信告知我们删除。邮箱: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 ☕.