You need to know

One of TypeScript’s core tenets is type-checking of the structure a value has. Interfaces are used to name types and define contracts or constraints for code or third-party code.

interface

When to use an interface, take a look at the following example.

function printLabel(labelledObj: { label: string }) {
   console.log(labelledObj.label);
}
Copy the code

The function printLabel takes one argument, and the argument object has a property of type String called label. The object arguments we pass in actually contain many attributes, but the compiler only checks if the required attributes exist and are of a matching type. But sometimes ts is not as loose, and that’s when things go wrong. If the constraint uses an interface, it looks like this:

interface LabelledValue {
    label: string
}

function printLabel(labelledObj: LabelledValue) {
    console.log(labelledObj.label);
}
let myObj = { size: 10.label: 'Size 10 Object' };
printLabel(myObj);
Copy the code

This interface describes the conditions to be satisfied by the passed parameter object as long as it contains an attribute of type string called label. Different from other languages, it can only be said that the object type format is constrained by the interface, not the object implementation excuse. As mentioned above, as long as the conditions are met, the order is not within the scope of inspection.

Optional attribute

Not all attributes in the interface are required. Some exist only under certain conditions, or not at all. At this point, you can use optional attributes. The interface with optional attributes is similar to the normal interface definition, except that the optional attribute name definition is followed by one. Symbols.

interface Square {
    color: string
    area: number } interface SquareConfig { color? : string width? : number }function createSquare(config: SquareConfig) :Square {
    let newSquare = { color: 'white'.area: 100 }
    if (config.color) {
        newSquare.color = config.color
    }
    if (config.width) {
        newSquare.area = config.width * config.width
    }
    return newSquare;
}
let mySquare = createSquare({ color: 'black' })
Copy the code

The SquareConfig interface allows, but does not require, parameter objects to have color and width attributes when describing the parameter constraints of the createSquare function.

Read-only property

Some object properties can only change their value when the object is created. Interface read-only properties:

interface Point {
    readonly x: number
    readonly y: number
}
// x,y cannot be changed
let p1: Point = { x: 10.y: 20 }
// p1.x = 5 // error
Copy the code

Generic read-only arrays:

let a: number[] = [1.2.3.4]
let ro: ReadonlyArray<number> = a
// ro cannot be modified, and
// a = ro // error Generic read-only arrays cannot be assigned to ordinary arrays
// Use type assertion if assignment is required
a = ro as number[]
Copy the code

Alternatively, const can declare read-only variables. That is, readonly is used for read-only attributes and const is used for variables.

Additional property checking

Look directly at the example:

interface SquareConfig { color? : string; width? : number; }function createSquare(config: SquareConfig) :{ color: string; area: number } {
    // ...
    return
}
let mySquare = createSquare({ colooour: "red".width: 100 }); 
Copy the code

As shown in the previous example, the createSquare call returns an error. You might think that the parameter object contains the wdith attribute, is of the correct type, has no color attribute, and that the colooour attribute can be treated as an extra meaningless attribute. Still, TS will consider this to be buggy. Object literals are treated specially and undergo “extra property checks” when they are assigned to variables or passed as arguments. You get an error if an object literal has any properties that the “target type” does not contain. This should be easy to understand. In the example above, when the createSquare function is passed an object created from an object literal, an error is reported after additional property checking. The colooour property is not a property of the target type. Solution 1: Use type assertions

let mySquare = createSquare({ colooour: "red".width: 100 } as SquareConfig);
Copy the code

Solution 2: Assign another variable

let opt = { colooour: "red".width: 100 };
let mySquare = createSquare(opt);
Copy the code

Unlike the literal approach, the opt object passed in does not undergo additional property checking.

Both methods can bypass the check, but a better way to do this is to add a string signature index to the SquareConfig interface:

interface SquareConfig { color? : string; width? : number; [propName: string]: any; }Copy the code

The only way to do this is to make sure that the object can have some extra attributes, so that it can have any number of attributes, and it doesn’t matter what type it is as long as it’s not color or width. The above techniques can be used to circumvent inspections when dealing with complex object structures, but should not be used in simple code. Instead, modify the interface definition to support additional attribute passing.

Function types

Interfaces can describe the various shapes that objects in JavaScript can have. In addition to describing ordinary objects with attributes, interfaces can also describe function types. To use the interface to represent function types, we need to define a call signature for the interface. The call signature is like a function definition with only the argument list and return value types.

interface SearchFunc {
    // Invoke the signature
    (source: string, subString: string): boolean
}
Copy the code

This interface is a function type interface. Let’s use this function-type interface.

let mySearch: SearchFunc = function (src: string, sub: string) :boolean {
    let result = src.search(sub)
    return result > -1
}
Copy the code

The parameter name of the function does not need to match the name defined in the interface. However, the parameter types at the corresponding locations must be compatible, and the return value type must be consistent with the interface definition.

Indexable type

Much like using interfaces to describe function types, we can also describe types that can be “indexed,” such as a[10] or ageMap[” Daniel “]. Indexable types have an index signature that describes the type of the object index and the corresponding index return value type. Index signature is divided into two types, one is digital index signature, the other is string index signature. Digital index signature is to define a digital index signature interface, through the number type index to get a specified type of return value.

interface StringArray {
    // Digital signature
    [index: number]: string
}

let myArray: StringArray = ['bob'.'fred'.'smith'];
let myStr: string = myArray[0];
Copy the code

String index signatures differ from digital index signatures only in their index types.

interface StringArray {
    // String index signature
    [index: string]: string
}

let myArray: StringArray = { 'a': 'aaa'.'b': 'bbb' };
let myStr22: string = myArray22['a'];
console.log(myStr22) // aaa
Copy the code

These are the two index formats supported by TS. Note that the numeric index return type must be a subtype of the string index return type, because the numeric index will be converted to a string. Such as:

let myStr: string = myArray[0];
/ / equivalent to
let myStr: string = myArray['0'];
Copy the code

Such as:

class Animal {
    name: string
}

class Dog extends Animal {
    breed: string
}

interface NotOkay {
    [x: number]: Dog // Digital signature
    [x: string]: Animal // String signature
    // The type returned by the digital signature is the subtype returned by the string signature index
}

let an1 = new Animal()
let do1 = new Dog()
let oo1: NotOkay = { 'bb': an1, 2: do1 }

console.log(oo1[2]) // Dog {}, when accessing a digital signature, converts the number to a string,
// Why is the return type of a digital signature a subtype of the return type of a string signature
console.log(oo1['bb']) // Animal {}
Copy the code

In addition, string index signatures do a good job of describing dictionary patterns, and they also ensure that all attributes match their return value types. Such as:

interface NumberDictionary {
  [index: string]: number;
  length: number;    // Yes, length is number
  name: string       // Error, the type of 'name' does not match the type of the index type returned value
}
Copy the code

Finally, you can set the index signature to read-only to prevent index assignment:

interface ReadonlyStringArray {
    readonly [index: number]: string;
}
Copy the code

Class types

Classes in TS can also implement interfaces that have some kind of constraint or contract.

interface ClockInterface {
    currentTime: Date
    setTime(d: Date)}// Class implementation interfaces must have properties and methods defined in the interface
class Clock2 implements ClockInterface {
    currentTime: Date
    constructor(h: number, m: number){}setTime(d: Date) {
        this.currentTime = d
    }
}
Copy the code

Classes have two types: the type of the static part and the type of the instance. The type that implements the interface is called an instance type. Classes implement interfaces, which describe only the public part of the class (i.e. the instance type of the class) and do not examine the private members of the class. The properties and methods that implement the interface belong to the instance type of the class. The constructor of a class is statically typed. A constructor interface that uses a constructor signature to define an interface and tries to define a class to implement that interface gets an error because, as stated above, the constructor exists in the static part of the class and the class only does type checking on the instance part. The following is a mistake:

interface ClockConstructor {
   // Constructor signature
   new(hour: number, minute: number)
}

class Clock implements ClockConstructor {
   currentTime: Date;
   constructor(h: number, m: number){}}Copy the code

How to use the constructor interface we’ve seen the instance interface, the constructor interface. You also know that a class cannot directly implement a constructor interface. So how do you use the constructor interface? Take a look at the following example:

// for instance methods
interface ClockInterface {
    tick()
}.
// used by the constructor
interface ClockConstructor {
    // Constructor signature
    new(hour: number, minute: number): ClockInterface
}

// The first argument is checked to see if it matches the constructor signature ClockConstructor
function createClock(ctor: ClockConstructor, hour: number, minute: number) :ClockInterface {
    return new ctor(hour, minute)
}

// Define two classes to implement instance interfaces
class DigitalClock implements ClockInterface {
    constructor(h: number, m: number){}tick() {
        console.log('tick Digital'); }}class AnalogClock implements ClockInterface {
    constructor(h: number, m: number){}tick() {
        console.log('tick Analog'); }}let digital = createClock(DigitalClock, 12.17)
let anglog = createClock(AnalogClock, 7.32)

Copy the code

Because the first argument to createClock is of type ClockConstructor, when createClock is called, DigitalClock and AnalogClock are checked to see if they match the constructor signature. Since both the DigitalClock and AnalogClock classes implement the ClockInterface interface, and the ClockConstructor interface signature returns a value of type ClockInterface, So the DigitalClock and AnalogClock classes meet the conditions.

Thus we implement a factory method that creates instances of the type passed in, and that type is constrained by the constructor interface.

Inherited interface

Interfaces can also inherit from each other by copying members from one interface to another, more flexibly dividing the interface into reusable modules. And can inherit multiple interfaces at the same time.

interface Shape {
    color: string
}

interface PenStroke {
    penWidth: number
}

interface Square extends Shape, PenStroke {
    sideLength: number
}

// An interface can inherit multiple interfaces to create a composite interface of multiple interfaces
let squre = {} as Square
squre.color = 'blue'
squre.sideLength = 10
squre.penWidth = 5.0
Copy the code

Mixed type

As we mentioned earlier, interfaces can describe the rich types in JavaScript. Because of the dynamic and flexible nature of JavaScript, sometimes you want an object to have multiple of the types mentioned above.

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter() :Counter {
    let counter = <Counter>function (start: number) {}; counter.interval =123;
    counter.reset = function () {};return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
Copy the code

In this example, getCounter returns an object of type Counter, which is both a function type and an object type with the reset method and interval properties.

* Interface inherits classes

Interface inherits class, use a few scenarios. When an interface inherits a class, it inherits its members, but not its implementation. Interfaces seem to declare all existing members of a class, but provide no implementation, and also inherit private and protected properties. This means that when an interface inherits from a class that has private or protected members, the interface can only be implemented by that class or its subclasses. Look at an example:

class Control {
    private state: any
}

interface SelectableControl extends Control {
    select()
}

// Declare Button class and inherit Control class, then implement SelectableControl interface
class Button extends Control implements SelectableControl {
    select(){}}// This class implements the interface without inheriting Control, which is not possible because it lacks private member state
// class ImageC implements SelectableControl {
// select() { }
// }
Copy the code

The SelectableControl interface inherits the Control class, so the interface also inherits the member states of the class. Only subclasses of the Control class can implement this interface. In the above code, the Button class inherits the Control class, so it naturally inherits the class member state, and then implements the SelectableControl interface, interface requirements to implement it in the class state member, Button meet, so this is correct. ImageC class does not inherit Control class to implement SelectableControl interface, interface check class has no member state, so error.

After the

So much for interfaces, which play a very important role in TS and are widely used.