What is an interface
TS Chinese document description
One of TypeScript’s core tenets is type-checking of the structure a value has. It is sometimes called “duck type discrimination” or “structural typing”. In TypeScript, interfaces name these types and define contracts for your code or third-party code.
When I first looked at TS, I felt abstract, so I put the concept aside and went through the whole document. Then I came to Learning typescript-interfaces and saw the definition and examples in this article, and suddenly I had a new understanding.
The following is a partial translation of the original text
Simply put, an interface is a way of describing the shape of an object. In TypeScript, we are only concerned with checking if the properties within an object have the types that are declared and not if it specifically came from the same object instance.
Simply put, an interface is a description of the shape of an object.
In TypeScript, we only care if an object has properties of a type we define, not if they are the same instance.
interface NameValue {
name: string
}
let thisObj = { name: 'Bryan' };
let thatObj = { name: 'Bryan' };
function printName(name: NameValue) {
console.log(name);
}
printName(thisObj); // 'Bryan'
printName(thatObj); // 'Bryan'
console.log(thisObj === thatObj) // false
Copy the code
To emphasize how TypeScript only checks the shape of objects, we have thisObj and thatObj with the same property, name. Despite being two different objects, both objects pass type checking when printName() takes each of them as arguments.
To highlight how Typescript checks only the shape of an object, the example creates thisObj and thatObj objects with the same property, although they are different objects that pass type checking when the printName method is called.
At this point, it makes sense to look at the definition of a duck (if you walk like a duck and quack, you’re a duck).
Summary of tutorial definitions with TutorialSteacher:
- An interface is a description of the shape of an object
- An interface defines the syntax of the class to follow
Typescript
Does not convert the interface toJavascript
Instead, the interface is used for type checking.- An interface can define a type or implement it in a class (where the implementation of the interface is reflected in the class type)
Interface attributes and types
- Optional attribute
- Read-only property
- Additional property checking
- Function types
- Indexable type
- Class types
- Implementing an interface
- The difference between the static and instance parts of a class
- Inherited interface
- Mixed type
- Interface inheritance class
Optional attribute
Grammar:
The property name? : Type description; eg: color? : string;
Application scenarios and functions: Pre-define possible attributes, and get an error type prompt for attributes that are not listed
As follows:
interfaceSquareConfig { color? :string; width? :number;
}
function createSquare(config: SquareConfig) :{ color: string; area: number } {
let newSquare = { color: "white".area: 100 };
if (config.clor) {
// ⚠️⚠️⚠️ Error: Property 'clor' does not exist on type 'SquareConfig'
newSquare.color = config.clor;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({ color: "black" });
Copy the code
Read-only property
Grammar:
Read-only object properties
Readonly property: type description; eg: readonly x: number;
Read-only array properties
ReadonlyArray<T>
eg:let arr:ReadonlyArray<number> = [1,2,3,4,6]
Application scenario: An attribute cannot be modified once created
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10.y: 20 };
p1.x = 5; / / ⚠ ️ ⚠ ️ ⚠ ️ Error! Cannot assign to 'x' because it is a constant or a read-only property.
Copy the code
Additional property checking
To allow an interface to accept attributes that do not exist, write:
- Recommended: String index signature
[propName:string]:any
interfaceSquareConfig { color? :string; width? :string;
[propName:string] :any;
}
const square:SquareConfig = {
color: 'red'.opacity: 0.5
} // OK
Copy the code
Typescript validates because the additional attribute is of type any.
- Assign an object with additional attributes to an object that is not described with additional attributes
interfaceSquareConfig { color? :string;
width: number;
}
let squareOptions= { width: 100.color: 'red'.opacity: 0.5 };
let squareOptions2:SquareConfig = squareOptions
Copy the code
This is ok because detection by assignment is looser than the literal form of an object, as long as the properties in the object are of the correct type for the interface description. Other additional attributes are not validated.
Function types
Interfaces can describe Function types as well as Object/Array types
Function Declaration (which describes the parameter type and return type)
interface PrintNums = {
(num1: number.num2: number) :void; // void means it does not return anything
}
let logNums: PrintNums;
logNums = (num1, num2) = > {
console.log(num1,num2);
}
Copy the code
The logNums function is no longer required to specify a type (logNums = (num1:number, num2:number):void => {}). TypeScript’s type system infers argument types by using logNums: PrintNums finds the type description for printNums.
Indexable type
Indexable types are types that can be indexed, such as a[0], a[‘hi’], and can be used to describe properties of arrays and object members.
The indexable type structure is shown in the figure below, with an index signature that describes the type of the object index and the corresponding index return value type.
Describing array members
interface numberArray {
[propname:number] :number
}
const nums:numberArray = [1.3.5]
Copy the code
Description object member
interface Obj {
[propname: number] :number.name:string,}const nums: Obj = {
0: 1.name: 'lisa'
}
Copy the code
TypeScript supports two index signatures: strings and numbers. The numeric return value must be a subclass of the string return value.
class Animal {
name: string;
}
class Dog extends Animal {
age:number
}
interface Ok {
[x: string]: Dog;
[x: number]: Animal; // Error : Numeric index type 'Animal' is not assignable to string index type 'Dog'.
}
Copy the code
Because javascript converts numeric indexes to strings when using indexes, Dog needs to be a subclass of Animal. The example above is just the opposite.
The correct way to rewrite this example is:
class Animal {
name: string;
}
class Dog extends Animal {
age:number
}
interface Ok {
[x: string]: Animal; // String index signature
[x: number]: Dog; // Digital index signature
}
const animal: Animal = new Animal()
animal.name='bird'
const dog: Dog = new Dog()
dog.age = 1;
dog.name = 'pop';
const animals: Ok = {
'cat': animal,
0: dog,
}
Copy the code
Note that if the index signature is specified, the return value of other index signatures of the same type in the interface must meet the return value of the index signature
interface AnimalDefinition {
[index: string] :string;
name: string; // Ok as return type is "string"
legs: number; // Error
}
Copy the code
Class types
Implementing an interface
Like languages like Java and C#, interfaces in TypeScript can be implemented using Class. The classes that implement the interface need to conform strictly to the structure of the interface.
interface IEmployee {
empCode: number;
name: string;
getSalary:(number) = >number;
}
class Employee implements IEmployee {
empCode: number;
name: string;
constructor(code: number, name: string) {
this.empCode = code;
this.name = name;
}
getSalary(empCode:number) :number {
return 20000; }}let emp = new Employee(1."Steve");
Copy the code
In the example above, the Employee class implements the interface IEmployee through the keyword Implement. Implementation classes should strictly define properties and functions with the same name and data type.
Typescript will report an error if the implementation class does not strictly adhere to the type of the interface.
Of course the implementation class can define additional properties and methods, but at least the properties and methods in the interface must be defined.
The difference between the static and instance parts of a class
I think the description of this part in the document is rather vague. Why does the example in the official website report an error
interface ClockConstructor {
new (hour: number.minute: number);
}
/ / Error:
// Class 'Clock' incorrectly implements interface 'ClockConstructor'.
// Type 'Clock' provides no match for the signature 'new (hour: number, minute: number): any'.
class Clock implements ClockConstructor {
currentTime: Date;
constructor(h: number, m: number){}}Copy the code
Difference between the static and instance sides of classes
The interface declares the method/members that the instances have, and not what the implementing class has.
That is, the interface declares only methods/attributes that the instance has, and constructor is not part of the instance part, so an error is reported following the documented example.
So two concepts need to be clarified
- Interfaces only declare methods/properties that the instance has.
constructor
Exists in the static part of the class, so it is not checked.
How do I implement the constructor section check? This stackOverflow article also provides an answer, referring to the Array implementation
interface Array<T> {
length: number;
toString(): string;
toLocaleString(): string; push(... items: T[]):number;
pop(): T | undefined; . [n:number]: T;
}
interface ArrayConstructor {
new(arrayLength? :number) :any[];
new <T>(arrayLength: number): T[];
new<T>(... items: T[]): T[]; (arrayLength? :number) :any[];
<T>(arrayLength: number): T[]; <T>(... items: T[]): T[]; isArray(arg:any): arg is Array<any>;
readonly prototype: Array<any>;
}
Copy the code
That is, the constructor part is defined separately from the example part, with Array being a description of the instance part and ArrayConstructor being a description of the constructor part.
Below is a breakdown of the official website example
interface ClockConstructor { // ClockConstructor description
new (hour: number.minute: number): ClockInterface;
}
interface ClockInterface { // ClockInterface instance description
tick();
}
// createClock creates a constructor check
function createClock(ctor: ClockConstructor, hour: number, minute: number) :ClockInterface {
return new ctor(hour, minute); // Ctor is the incoming class, in this example DigitalClock and AnalogClock
// DigitalClock and AnalogClock implement the ClockInterface, so createClock returns type ClockInterface
}
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"); }}let digital = createClock(DigitalClock, 12.17);
let analog = createClock(AnalogClock, 7.32);
Copy the code
Since Constructor is triggered when a new class is created, the createClock function is created to implement type checking on Constructor via new ctor. Because the classes DigitalClock and AnalogClock are implementations of the ClockInterface, the createClock function returns type ClockInterface.
Inherited interface
Similar to class inheritance, interfaces can also inherit.
This allows you to replicate other interfaces and extend them.
An interface can also inherit multiple interfaces.
interface Shape {
stokeWidth: number;
}
interface PenColor {
color: string;
}
interface Circle extends Shape, PenColor {
solid: boolean
}
const circle: Circle = {
stokeWidth: 1.color: 'red'.solid: false
}
Copy the code
Mixed type
There may be scenarios where an object can be used as both a function and an object, with additional attributes. Such as:
function Vue (options) {
if(process.env.NODE_ENV ! = ='production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options)
}
Vue.version = '__VERSION__'
export default
Copy the code
You can use the following methods to describe interfaces in this scenario
interface Counter {
(start: number) :string;
interval: number;
reset(): void;
}
function getCounter() :Counter {
let counter = <Counter>function (start: number) {};// Assertions are used here
counter.interval = 123;
counter.reset = function () {};return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
Copy the code
The Counter interface mixes descriptions of functions, function properties, and assertions in the examples on the official website
let counter = <Counter>function (start: number) { };
If you remove an assertion, you get an error
The error message mentions that the attribute interval is missing, but with assertions, it loses the significance of checking the function. In the example, the function that defines the interface returns a string, but no error is reported after assertions.
Object.assign
(T: T, U: U); object. assign
(T: T, U: U
interface Counter {
(start: number) :string;
interval: number;
reset(): void;
}
let c: Counter = Object.assign(
(start:number) = > { return start+' ' },
{
interval: 10.reset: (a) :void= > { }
}
)
c(10);
c.reset();
c.interval = 5.0;
Copy the code
Assign
(T: T, U: U) returns T & U
Interface inheritance class
// working
The resources
- tutorialsteacher-typescript-interface
- Learning TypeScript — Interfaces
- difference-between-the-static-and-instance-sides-of-classes
- Build a function object with properties in TypeScript