Type of object-interface

In TypeScript, we use Interfaces to define the type of objects.

Type of object-interface

What is an interface

In object-oriented languages, Interfaces is a very important concept. It is an abstraction of behavior, and how to act specifically needs to be implemented by classes. The interface in TypeScript is a very flexible concept. In addition to abstracting part of the behavior of a class, it is also often used to describe the "shape of an object".

simple example

interface Person {
   name: string;
   age: number
}

let bobo: Person = {
   name: "bobo",
   age: 30
}

In the above example, we define an interface Person, and then define a variable tom whose type is Person. In this way, we constrain the shape of the tom to be consistent with the interface Person. The first letter of the interface is generally capitalized. Some programming languages ​​suggest that the name of the interface is prefixed with I.

It is not allowed to define variables that have fewer attributes than interfaces:

interface Person {
    name: string;
    age: number;
}

let tom: Person = {
    name: 'Tom'
};

// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
//   Property 'age' is missing in type '{ name: string; }'.

More attributes are not allowed:

interface Person {
    name: string;
    age: number;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.

It can be seen that when assigning values, the shape of the variable must be consistent with the shape of the interface.

Optional attributes Sometimes we want to not match a shape exactly, then we can use optional attributes:

interface Person {
    name: string;
    age?: number;
}

let tom: Person = {
    name: 'Tom'
};
interface Person {
    name: string;
    age?: number;
}

let tom: Person = {
    name: 'Tom',
    age: 25
};

The meaning of the optional attribute is that the attribute may not exist. At this time, it is still not allowed to add undefined attributes: At this time, it is still not allowed to add undefined attributes:

interface Person {
    name: string;
    age?: number;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// examples/playground/index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.

Arbitrary attribute

Sometimes we want an interface to allow arbitrary attributes, we can use the following methods:

interface Person {
  name:string;
  age?: number;
  [propName: string]: any
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
}

Use [propName: string] to define any property to take the value of type string. It should be noted that once any attribute is defined, the type of the determined attribute and optional attribute must be a subset of its type:

interface Person {
    name: string;
    age?: number;
    [propName: string]: string;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Index signatures are incompatible.
//     Type 'string | number' is not assignable to type 'string'.
//       Type 'number' is not assignable to type 'string'.

In the above example, the value of any attribute is allowed to be string, but the value of the optional attribute age is number, which is not a sub-attribute of string, so an error is reported. In addition, it can be seen in the error message that the type of {name:'Tom', age: 25, gender:'male'} is inferred to be {[x: string]: string | number; name: string; age: number; gender: string; }, this is a combination of union type and interface. Only one arbitrary attribute can be defined in an interface. If there are multiple types of attributes in the interface, you can use the union type in any attribute:

interface Person {
    name: string;
    age?: number;
    [propName: string]: string | number;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

Read-only attribute Sometimes we hope that some fields in the object can only be assigned when they are created, so we can use readonly to define read-only attributes:

interface Person {
   readonly id: number;
   name: string;
    age?: number;
    [propName: string]: any;
}
let tom: Person = {
    id: 89757,
    name: 'Tom',
    gender: 'male'
};

tom.id = 9527;

// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

In the above example, after the attribute id defined by readonly is initialized, it is assigned again, so an error is reported. Note that the read-only constraint exists when assigning a value to an object for the first time, not when assigning a value to a read-only property for the first time:

interface Person {
   readonly id: number;
   name: string;
   age?: number;
   [propName: string]: any;
}

let tom: Person = {
   name: 'Tom',
   gender: 'male'
}

tom.id = 9527

// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.
//   Property 'id' is missing in type '{ name: string; gender: string; }'.
// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

In the above example, there are two error messages. The first is when the value is assigned to the tom, the id is not assigned. The second place is when assigning a value to tom.id, because it is a read-only attribute, an error is reported