Measuring the progress of a program by lines of code is like measuring the progress of an airplane by weight.


I came across these two concepts while reviewing TypeScript documentation recently, and the last example left me scratching my head as to why I did it. Here’s how to interpret the example.

Indexable Types Indexable Types

TS3 Documents TS4 documents

Instead of using interfaces to describe attributes and methods, interfaces can also describe elements or attributes retrieved by index, such as arr[0], obj[‘name’]. Indexable types have an index alias (key) that describes the type used to index an element or attribute and the corresponding index return value type.

// Get the array element with the index
interface StudentArray {
  [index: number] :string;
}
let studentArr: StudentArray = ["Bob"."Fred"];
let student1: string = studentArr[0]; // 'Bob'
let student2: string = studentArr[1]; // 'Fred'

// Get object attributes with the index
interface StudentObject {
  [key: string] :number;
}
let studentObj: StudentObject = {};
studentObj['Bob'] = 1;
studentObj['Fred'] = 2; // { Bob: 1, Fred: 2 }
Copy the code

This is because the JavaScript language dictates that all key names of objects should be strings. When an object is retrieved using an index, the non-string index is automatically converted to a string. Because the array itself is an object, the index (subscript) of the array is actually a string. (See: ECMA5.1 Property Accessor)

interface Animal {
  type: string;
}

interface Dog extends Animal {
  name: string;
}

interface DogData {
  [x: number]: Dog;
  [x: string]: Animal;
}

let hotDog: DogData = {};
let animal: Animal = { type: 'dog' };
let dog: Dog = { type: 'dog'.name: 'hotDog' };
hotDog['0'] = animal;
hotDog[0] = dog;
// error TS2741: Property 'name' is missing in type 'Animal' but required in type 'Dog'
Copy the code

However, even if the value of the number index is a subtype, the two indexes cannot have the same name. Otherwise, the runtime will report an error unless the data structure of the subtype is identical to that of the parent type.

Class Type Indicates the class type

TS3 documentation (deprecated in TS4 documentation)

Implementing an interface

TypeScript can use it to explicitly force a class to implement an interface, just as interfaces do in C# or Java.

// Define the ClockInterface that contains the currentTime attribute
interface ClockInterface {
    currentTime: Date;
}

// The Clock class implements the ClockInterface interface and creates the constructor
class Clock implements ClockInterface {
    currentTime: Date;
    constructor(h: number, m: number){}}Copy the code

Or describe a setTime method in an interface and implement it in a class.

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

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number){}}Copy the code

Interfaces describe only the public parts of a class, which are defined by the class itself

Class static parts and instance parts

Classes have two types: the static part of the type (constructor) and the instance part of the type (properties, methods). If you want to create a class, implementing an interface with a constructor will result in an error

interface ClockConstructor {
  // Describe the constructor
  new (hour: number.minute: number);
}

// Class "Clock" error implements interface "ClockConstructor".
// The content provided by Clock does not match the signature new (hour: number, minute: number): any.
class Clock implements ClockConstructor {
  currentTime: Date;
  constructor(h: number, m: number){}}Copy the code

This error is reported because when a class implements an interface, TS does type checking only for the instance part (properties, methods), whereas Constructor is the static part of the class and is outside the scope of TS’s check.

To implement type checking on the static part of a class, you need to deal directly with the static part of the class. Start by defining two interfaces, one ClockConstructor for describing constructors and one ClockInterface for describing instance methods and properties. Second, define a function createClock that creates an instance within the function and returns it.

interface ClockConstructor {
  // Describes the parameter type and return type of the constructor
  new (hour: number.minute: number);
}

// Pass in the ClockConstructor type and hour and minute parameters
function createClock(ctor: ClockConstructor,  hour: number,  minute: number) {
  // New an instance of the ClockConstructor type
  // Pass the constructor arguments hour and minute
  return new ctor(hour, minute);
}
Copy the code

This is possible. Changing the createClock hour and Minute types will result in an error, but the createClock method will not work because the ClockConstructor interface cannot be implemented.

interface ClockConstructor {
  // Describes the parameter type and return type of the constructor
  new (hour: number.minute: number): ClockInterface;
}

interface ClockInterface {
  showTime(): void;
}

// Pass in the ClockConstructor type and hour and minute parameters to return the ClockInterface type
function createClock(ctor: ClockConstructor,  hour: number,  minute: number) :ClockInterface {
  // New an instance of the ClockConstructor type
  // Pass the constructor arguments hour and minute
  // ClockConstructor returns the ClockInterface type
  return new ctor(hour, minute);
}
Copy the code

This code simulates the interface definition, and createClock combines the ClockConstructor and ClockInterface interfaces.

interface ClockConstructor {
  // Describes the parameter type and return type of the constructor
  new (hour: number.minute: number): ClockInterface;
}

interface ClockInterface {
  showTime(): void;
}

// Pass in the ClockConstructor type and hour and minute parameters to return the ClockInterface type
function createClock(ctor: ClockConstructor,  hour: number,  minute: number) :ClockInterface {
  // New an instance of the ClockConstructor type
  // Pass the constructor arguments hour and minute
  // ClockConstructor returns the ClockInterface type
  return new ctor(hour, minute);
}

// Since the ClockConstructor constructor needs to return the ClockInterface type,
// So the ClockImpl data structure should include both ClockConstructor and ClockInterface's constructors, attributes, and methods
class ClockImpl implements ClockInterface {
  hour: number;
  minute: number;
  constructor(h: number, m: number) {
    this.hour = h;
    this.minute = m;
  }
  showTime() {
    console.log(`Time now: The ${this.hour}:The ${this.minute}`); }}// Create a clock instance
let clock = createClock(ClockImpl, 12.17);
clock.showTime();
Copy the code

Because the first argument to createClock is of ClockConstructor type, in createClock(ClockImpl, 12, 17) the ClockImpl matches the constructor is checked.

Finally, understand that the purpose of this is to be able to describe constructors in interfaces and implement them.

See GitHub’s discussion: Constructor in interfaces throws error

And Stack Overflow: How does interfaces with construct signatures work?

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

interface ClockInterface {
  showTime(): void;
}

The Clock class is of type ClockConstructor and implemented from the ClockInterface interface, so the data structure should include both
const Clock: ClockConstructor = class Clock implements ClockInterface {
  hour: number;
  minute: number;
  constructor(h: number, m: number) {
    this.hour = h;
    this.minute = m;
  }
  showTime() {
    console.log(`Time now: The ${this.hour}:The ${this.minute}`); }};let clock = new Clock(12.17);
clock.showTime();
Copy the code