This is the third day of my participation in the November Gwen Challenge. Check out the details: the last Gwen Challenge 2021

preface

Interfaces are confusing when you first move from Javascript to Typescript. Function XXX () {} uses the keyword interface. But what good is that?

interfacePerson { name? :string.age: number.sayHi: () = > string
}
interface SetUser {
 (name: string.age: number) :void;
}
Copy the code

In conjunction with TypeScript’s ability to add type checking to Javascript, we can see that interfaces define a “template” for variables, a set of declarations of properties and abstract methods that fulfill TypeScript’s static type checking needs.

We can define properties and methods in interfaces, which are then used when defining variables (describing an object or function).

const man: Person = {
  name: "Tom".age: 18.sayHi: () = > "Hi"
}
Copy the code

In addition, in object-oriented programming, interfaces make it possible to extend and implement.

Similar keywords are type:

typeUser = { name? :string
  age: number
};
type SetUser = (name: string.age: number) :void;
Copy the code

Next, I’ll show you how to use an interface.

Optional attribute

interfaceSquareConfig { color? :string; width? :number;
}
Copy the code

Read-only property

Some object properties can only change their value when the object is created.

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

TypeScript has the ReadonlyArray

type, which is similar to Array

except that all mutable methods are removed, thus ensuring that arrays can never be modified after they are created:

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
Copy the code

It is also not possible to assign the entire ReadonlyArray to a normal array, but you can override it with type assertions:

a = ro; // error!
a = ro as number[];
Copy the code

readonly vs const

The easiest way to determine whether to use readonly or const is to use it as a variable or as a property. Const as a variable, readonly as an attribute.

Additional attribute checking

Object literals are treated specially and subject to additional property checks. You get an error if an object literal has any properties that the “target type” does not contain.

interfaceSquareConfig { color? :string; width? :number;
}
function createSquare(config: SquareConfig) :{ color: string; area: number } {
  // ...
}
let mySquare = createSquare({ colour: "red".width: 100 }); // error: 'colour' not expected in type 'SquareConfig'
Copy the code

Getting around these checks is simple. The easiest way is to use type assertions:

Let mySquare = createSquare({width: 100, opacity: 0.5} as SquareConfig);Copy the code

The best way to do this is to add a string index signature, if you can be sure that the object may have some additional properties for a specific purpose.

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

One final way to skip these checks is to assign this object to another variable, because squareOptions doesn’t go through additional property checks, so the compiler doesn’t report an error.

let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);
Copy the code

Function types

Interfaces can describe function types as well as ordinary objects with attributes.

interface SearchFunc {
  (source: string.subString: string) :boolean;
}
let mySearch: SearchFunc = function(src: string, sub: string) :boolean {
  let result = src.search(sub);
  return result > -1;
}
let a = mySearch('abc'.'bc')
console.log(a) // true
Copy the code

For type checking of function types, the parameter name of a function does not need to match the name defined in the interface, but only the parameter type in the corresponding position. Source -> SRC, subString -> sub, as shown above.

Indexable type

Indexable types have an index signature that describes the type of the object index and the corresponding index return value type. Such as:

interface StringArray {
  [index: number] :string;
}
let myArray: StringArray = ["Bob"."Fred"];
let myStr: string = myArray[0];
console.log(myStr);  // "Bob"
Copy the code

The index signature indicates that when StringArray is indexed by number, it returns a value of type String.

TypeScript supports two index signatures: strings and numbers. Both types of indexes can be used, but the return value of the numeric index must be a subtype of the return value type of the string index. Because when you index a number, JavaScript converts it to a string and then indexes the object, the two need to be consistent.

In the following example, the type of name does not match the type of the string index, so the type checker gives an error:

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, thus preventing index assignment:

interface ReadonlyStringArray {
    readonly [index: number] :string;
}
let myArray: ReadonlyStringArray = ["Alice"."Bob"];
myArray[2] = "Mallory"; // error! You cannot set myArray[2] because the index signature is read-only.
Copy the code

Class types

Class implementation interface

interface ClockInterface {
  currentTime: Date;
  setTime(d: Date) :void;
}
class Clock implements ClockInterface {
  currentTime: Date;
  setTime(d: Date) {
    this.currentTime = d;
  }
  constructor(h: number, m: number) {
    this.currentTime = new Date(a);// Property 'currentTime' has no initializer and is not definitely assigned in the constructor.(2564)}}Copy the code

Interfaces describe the public parts of a class, not the public and private parts. It doesn’t help you check if the class has some private members.

To remove initialization hint: tsconfig. Json > compilerOptions, adding a: “strictPropertyInitialization” : false, without having to initialize the properties.

The difference between the static and instance parts of a class

Example:

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

When you use a constructor signature to define an interface and try to define a class that implements the interface, you get an error Type ‘Clock’ provides no match for the signature ‘new (hour: number, minute: Number): any’, because classes have two types: the type of the static part and the type of the instance. When a class implements an interface, only the instance parts are type checked. Constructor exists in the static part of the class and is therefore outside the scope of inspection. Therefore, we should directly manipulate the static part of the class.

In the following example, we define two interfaces, ClockConstructor for the constructor and ClockInterface for the instance method. To facilitate our definition of the function createClock, it creates an instance with the type passed in.

interface ClockConstructor {
  new (hour: number.minute: number): ClockInterface;
}
interface ClockInterface {
  tick(): void;
}
class DigitalClock implements ClockInterface {
  constructor(h: number, m: number){}tick() {
    console.log("beep beep"); }}class AnalogClock implements ClockInterface {
  constructor(h: number, m: number){}tick() {
    console.log("tick tock"); }}function createClock(ctor: ClockConstructor, hour: number, minute: number) :ClockInterface {
  return new ctor(hour, minute);
}
let digital = createClock(DigitalClock, 12.17);
let analog = createClock(AnalogClock, 7.32);
digital.tick();
analog.tick();
Copy the code

Because the first argument to createClock is of type ClockConstructor, in createClock(AnalogClock, 7, 32), AnalogClock is checked to see if it matches the constructor signature.

Inherited interface

Like classes, interfaces can inherit from each other. This allows us to copy members from one interface to another, giving us more flexibility to split the interface into reusable modules.

interface Shape {
  color: string;
}
interface Square extends Shape {
  sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
Copy the code

Note: Square in Angle brackets is a generic assertion, and

{} is a Type assertion. Let square = {} as square;

An interface can inherit multiple interfaces to create a composite interface of multiple interfaces.

interface Shape {
  color: string;
}
interface PenStroke {
  penWidth: number;
}
interface Square extends Shape, PenStroke {
  sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
Copy the code

Mixed type

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.

An example is an object that can be used as both a function and an object, with additional attributes.

interface Counter {
  (start: number) :string;
  interval: number;
  reset(): void;
}
function getCounter() :Counter {
  let counter = <Counter>function (start: number) {
    console.log(start);
  };
  counter.interval = 123;
  counter.reset = function () {};return counter;
}
let c = getCounter();
c(10);
c.interval = 5.0;
c.reset();
Copy the code

Interface inheritance class

When an interface inherits a class type, it inherits the members of the class but not its implementation.

Interfaces also inherit to the private and protected members of the class. This means that when you create an interface that inherits a class with private or protected members, the interface type can only be implemented by that class or its subclasses (Implement).

class Control {
  private state: any;
}
interface SelectableControl extends Control {
  select(): void;
}
class Button extends Control implements SelectableControl { // Button is a subclass of Control
  select(){}}// Error: The "Image" type is missing the "state" attribute
class Image implements SelectableControl { // Image is not a subclass of Control
  select(){}}Copy the code

The SelectableControl contains all members of Control, including the private member state. Since state is a private member, only subclasses of Control can implement the SelectableControl interface.

reference

  • Interface · TypeScript Chinese Language