Structure inspection

Before we talk about interfaces, it’s important to understand that TS often checks data structures

One of TypeScript’s core tenets is type-checking of the structure a value has

Understand this statement in code.

function man(person: {name: string, age: number}) {
      console.log(person.name, person.age)
}
Copy the code

The person argument to the man function can only accept objects with name: string and age: number. This is called structure

function man(person: {name: string, age: number}) {
      console.log(person.name, person.age)
}
man({
   name: 'lisa'.age:18       // ok
})
Copy the code

If the value passed does not conform to {name: string, age: number} and the type is incorrect, an error is reported.

The value passed in must conform to the requirements. This is called type checking for the structure of a value and there are structure checking and type checking

The following interface error cases are based on this.


interface


Interfaces are used to describe data structures. It can also be understood as a code specification that has the function of constraining code

For example, write the code above to the interface.

Use the interface keyword to define the interface

interface Iperson {
   name: string,
   age: number
}
function man(person: Iperson) {
   console.log(person.name, person.age)
}

/ / equivalent
function man(person: {name: string, age: number}) {
      console.log(person.name, person.age)
}
Copy the code

Iperson is the name of the interface, and the interface is like our custom type, which is used like a type. TS also checks that our code character does not conform to the specification in the interface

interface Iperson {
   name: string,
   age: number
}
function man(person: Iperson) {
   console.log(person.name, person.age)
}

man({
   name: 'lisa'.age:18       // ok
})
man({
   name: 'lisa'.age:'men'   // The error type is incompatible
})

Copy the code

The code in the interface does not provide a concrete implementation. Only structure types can be defined (xx values are of type XX which is OK)

The function interface


In addition to describing ordinary objects with attributes, interfaces can also describe the types of functions.

Define the parameter list and return value type of the function in the interface. Each parameter needs a name and type.

interface Person_2 {
// Define the parameter type and return value type of the function
(name: string, age:number):void
}
Copy the code

Once defined, we can use this interface to create a function whose parameter name does not need to match the name defined in the interface:

let func1: Person_2 = (sex:string, num:number):void= > {}  //ok parameter type match, name does not matter
let func2: Person_2 = (sex:string, num:string, dd:string):void= > {}  // Error writes an extra argument. Structural mismatch
Copy the code

Let’s implement a more complex function-type interface


interface Actual_1 {   
  name: string
  age: number
}
interface Person_3 {
// Use ES6 destruct assignment to get parameters
({name, age}: Actual_1): void / /. Here the Actual_1 interface is primarily constrained by arguments. Arguments can only take name and age attributes
 } 

// Next, implement this function according to the given interface
// Correct version:
// The parameters of this function are defined in the interface by the destruct assignment of ES6 objects. If you want to change the parameter name, you need to use object destruct assignment syntax.
let func: Person_3 = ({name: Iname, age}: Actual_1): void= > {  // == {name: Iname, age} = {name:xxx, age:xxx}
    console.log(`${Iname}.${age}`) // ok
}
func({
  age: 18.name :'boolean'
})

// Error version:
let func: Person_3 = ({name, iage}: Actual_1): void= > { // Error Actual_1 does not define iage.
    console.log(`${name}.${iage}`)                     
}
func({
  age: 18.name :'boolean'}) -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -// The above function is not implemented as an interface:
let func= ({name, age}:{name: string, age: number}): void= > {
    console.log(`${name}.${age}`)
}
func({
  age: 18.name :'boolean'
}) //ok

// The interface is reusable and extensible.
// The Person_3 interface above can be used to constrain many of the same functions. Or use the INTERFACE Actual_1 directly to constrain the object.
Copy the code

Optional attribute


The point of the optional attribute is that it doesn’t matter if you don’t use it, because if you do use it, it’s bound to be of the correct type

To get a feel for the extensibility of the interface, let’s copy the code above

interface Actual_1  {   
  name: string
  age: number
}
// Think about how to extend the use of the sex property based on this.

interface Actual_1  {   
  name: string
  age: number
  // sex: string // This doesn't work because a lot of the code above implements this interface. Adding this property directly causes other code to have to implement it. And that leads to a whole bunch of mistakessex? : string// ok Optional attribute. Add a question mark (?) after the attribute name and before the colon. , indicates that the property is optional. Optional attributes.
}
let obj1: Actual_1 = {
    name: 'dd'.age: 90.sex: 'man'  //ok
}
let obj2: Actual_1  = {
    name: 'dd'.age: 90  //ok optional attributes are optional
}
let obj3: Actual_1 = {
    name: 'dd'.age: 90.sex: 123  // Error must comply with the specification
}
Copy the code

Interface inheritance


You can also extend the above example using interface inheritance

Using extends: Extends interface

interface Actual_1  {   
  name: string
  age: number
}
interface superActual extends Actual_1 {
    sex: string  
   // It looks like this
   // name: string
   // age: number
   // sex: string
}  

// Now there are three properties on superActual
// Implement it
let obj: superActual = {
    name: 'dd'.age: 90.sex: 'man'  //ok
}
Copy the code

An interface can also inherit multiple interfaces to merge interface members. Separate the interfaces to be inherited with commas.

Interface extends interface 1 and interface 2..... Interface n

It is important to note that although TS supports inherited interfaces, they will not compile if the inherited interface defines attributes of the same name but of a different type

Read-only property


Read-only property: A property that can only be read but cannot be modified

Define read-only properties: Prefix the property with the readonly keyword

interface Immutable  {
  prop: string  
  readonly x: number
  readonly y: number
}
let num: Immutable = {
    x: 100.// It cannot be modified after the assignment
    y: 99.prop: 'xxx'
}
num.prop = 'aaa'  //ok
num.x = 1  // Error can only be read and cannot be modified
num.y = 2 //error
Copy the code

The index sign


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

To clarify: indexes also have types, such as a[10] and a[’10’]. Index of type number and index of type string, and what type of value is returned through these indexes

Syntax: [prop: string]// Prop and index are arbitrary names, such as A, B, c and d
[index: number]
// The index signature can be either number or string
Copy the code

Let’s define an interface with a string index

interface oop {   
  readonly [prop: string]: any   // Returns any type of value by string index
}
// This interface implementation looks like a normal object... Because the index of an object is a string
let obj: oop = {
    a: 1.b: 2.c: 'e'
} 
// Now all properties in obj are read-only
obj.a      / / 1
obj['b']   / / 2
obj.c = 2  // Error cannot be modified.
Copy the code

Define an interface with a numeric index

interface numberArray { 
    [index: number]: number
   // Key is a number type: value is a number type
}
let numArr1: numberArray = [1.2.3.55.6.'1'] // Error cannot assign a string to type number
let numArr1: numberArray = [1.2.3.55.6]   // Similar to array of type number
let numArr2: number[] = [1.2.3.45.6.6]   // Array of type number
// Both look similar and can be evaluated and modified
numArr1[1] / / 1
numArr2[1] / / 1
numArr1[1]  = 77 //ok
numArr1[1] / / 77

// The array method cannot be used
numArr1.forEach()  The forEach method does not exist on the error numberArray type. Custom types, of course, don't exist
numArr2.forEach() // ok
Copy the code

To define some complex data, the following defines a set of user data:

interface UserJSON {
  [username: string]: {
    id: number
    age: number
    sex: string
  }
}
// implement it.
let obj: UserJSON = {
  Lisa: {
    id: 1.age: 18.sex: 'man'
  },
  Bob: {
    id: 2.age: 19.sex: 'man'
  },
  Sam: {
    id: 3.age: 20.sex: 'man'}}Copy the code

Note for index signature:

1. The same index signature cannot be repeated

2. When a string index signature is defined, all other members must conform to the string index signature specification

interface StrInter {
  [key: string]: number // This is a string index signature. It returns a value of type number.
  a: string // Error: attribute 'A' of type 'string' cannot be assigned to string index 'number'.
}

// Assume that the interface can be used
let obj: StrInter = {
     // When this interface is implemented, it is specified that the string index can only accept values of type number
     // Attribute a essentially exists as a string index
     // So the a attribute in the interface must be written according to the string index specification, specifying either type number or anyC:12.a: 'haha'  // What's the matter with little brother?? You accept a string value
}
// Modified version:
interface StrInter {
  [key: string]: number 
  a: number    //ok
  //a: any //ok} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -// Index signatures of type number are less problematic
interface NumInter {
  [key: number]: number // Other attributes will not be affected
  a: string    //ok
  }
 let arrObj: NumInter = {
    1:1.2:2.a: 'true'.// The a attribute is also assigned as is
    3:3      
 } //ok

 arrObj[1] / / 1
 arrObj['1'] // 1 Because object values are toString, converting properties to strings
 arrObj['1'] === arrObj[1]  //true 
 arrObj[a] // 'true'
Copy the code

3. If both index signatures are used together, they must be of the same type as the return value of string index signatures.

interface User1 {
 [b: number]: number // Error return value types are incompatible
 [a: string] : string
}
// The return value of the index type of the interface above causes a conflict, because in JavaScript numeric indexes are converted to string indexes (implicit type conversions).
// The following code is correct in JS
const a = [1.2.3];
a[1] === a['1'] // true------------------------------------------------------------------------------------------------------------------------ ----// Correct version:
interface User2 {
 [b: number]: string //ok 
 [a: string]: string
}

let obj: User2 = {
   1 : 'hah'.// Implicit type conversion is involved
   a : '2'.c : '3' 
}
obj[1]   // 'hah'
obj['1'] // 'hah'

// The numeric index return type is a subtype of the string index return type
interface User2 {
 [b: number]: null / undefined / never / any // All of these types are ok, but meaningless.
 [a: string]: string
}
Copy the code

function


TS is nothing special just like regular JS. Also supports ES6 arrow functions, default arguments,… Operators and so on

/ / function declaration
function add1(arg1: number, arg2: number) :number {
    return arg1 + arg2
 }
 // Arrow function
let add2 = (arg1: number, arg2: number): number= > arg1 + arg2
------------------------------------------------------------------------------------------------------------------
Add1: XXX,add2: XXX,add2: XXX No type specified
// Instead of assigning directly. This is because the type inference of TS, which we'll get into more slowly, helps us infer the structure of the function

// This is the actual writing of the above (take add2 for example). First specify the structure of the function, in the implementation of the function
let add2: (x: number, y: number) = > number  // where => number is the type of return value, not the body of the function

 // Implement functions that do not restrict the names of parameters
add2 = (arg1: number, arg2: number): number= > arg1 + arg2

// Note that the return value type is not explicitly specified below. But TS does type inference, and it concludes that it's number. Because number + number must be the return number type.
// 1 + 1 = 2???? Make it a string
add2 = (arg1: number, arg2: number) = > arg1 + arg2  

// The function structure on the left side of the equals sign, and the function implementation on the right side
let add2: (x: number, y: number) = > number  = (arg1: number, arg2: number): number= > arg1 + arg2
As you can see, type inference saves us a lot of code
------------------------------------------------------------------------------------------------------------------
Copy the code

Type the alias


It’s to give some types different names instead

Use the type keyword to declare it

Just like calling others Xiao Zhang or Xiao Li for convenience in daily life. Not by full name

// Assume that this (x: number, y: number) => number function type needs to be used many times. Do you have to write a bunch of code every time?
// We could use a shorter name
type AddFunction = (arg1: number, arg2: number) = > number  
// replace (arg1: number, arg2: number) => number with AddFunction

let myFunc2: AddFunction = (arg1: number, arg2: number) = >arg1 + arg2 ------------------------------------------------------------------------------------------------------------------------ ---// You can also use the above interface knowledge to encapsulate.
interface MyFunc {
    (x: number, y: number) :number
}

let myFunc1: MyFunc = (arg1: number, arg2: number) = > arg1 + arg2  

Copy the code

Type aliases look like interfaces, but are less flexible and extensible (inheritance) than interfaces.


interface AddFunct {
  (arg1: number, arg2: number) : number;
  a: string;
}

type AddFunction = {
  (arg1: number, arg2: number) : number;
  a: string;
}  //ok, but using type aliases in this way is not recommended.

// Type aliases are better for storing long type literals without any complicated logic.
type Tarr = [string, number, boolean]   
type Itype = string | number | null | boolean   // This is associative type syntax, which belongs to advanced type knowledge. This is used for code demonstration purposes
Copy the code

Optional parameters


The optional parameter of TS must come after the mandatory parameter; otherwise, errors will be saved

Syntax is the same as optional attributes

type AddFun = (arg1? : number, arg2: number, arg3: number) = > number // Error Optional parameters must be followed by mandatory parameters

type AddFun = (arg1: number, arg2: number, arg3? : number) = > number; //ok

let addFunc:AddFun          
addFunc = (x: number, y: number) = > x + y  //ok Optional arguments are optional
addFunc = (x: number, y: number, z: number) = > x + y + z //ok
Copy the code

The default parameters


The syntax is the same as the default arguments of ES6 functions

let addFun = (x:number, y:number, z:number = 3) :number= > x + y + z
addFun(1.2) / / 6

// This function can be simplified a little more. Let TS use type inference to help us identify some code types
let addFun = (x:number, y:number, z = 3) = > x + y + z   //TS will automatically infer from the default that z is of type number
addFun(2.2) / / 7
addFun(2.2.'2')// The error string cannot be typed as number
Copy the code

The remaining parameters


Also ES6 knowledge…

const func = (. args: number[]) = > console.log(args)
func(1.2.3.5.6) / / ok,2,3,5,6 [1]
func(1.2.'3'.5.'6') // The error type does not match. The string cannot be assigned the number type

// Args is of type any[]
const func1 = (arg1:number, ... args: any[]) = > {
  args.push(arg1)
  console.log(args)
}
func1(1.2.3.5.6) / / ok,3,5,6,1 [2]
func1(1.2.'3'.5.'6') //ok [2, '3', 5, '6', 1] any array can hold all types of values
Copy the code

overloading


TS function overloading is not the same as c++, Java, etc. In c++, in Java, overloading is actually using the same function name, passing in different numbers of arguments or different types of arguments, so that the function with the same name behaves differently.// Code from Wikipedia:
public class Test{
    public void A(){                // This is A function with no formal argument named A.
        
    }
    public void A(int a){           // This function has a function of type int, so it is overloaded.
        
    }
    public void A(String a){        // The data type of this function is String. The data type of the formal parameter is different, so it constitutes an overload.
        
    }
    public void A(int a,int b){     // This function takes two formal arguments, so it constitutes an overload.Don't worry about the syntax. As you can see, the function A up here depends on the change in parameters. Can do a lot of different things. -------------------------------------------------------------------------------------------------------------------- Javascript has no concept of function overloading. Therefore, it is common to imitate and achieve similar behavior by judging function parameters.function func(x) {
  if(typeof x === 'number') {
    console.log(x)
  } else if (typeof x === 'string') {
    console.log(x.split(', '))}else {
    // xxxxxx
  }
}
func(2)
func('hello')
Copy the code

Function overloading in TS, on the other hand, is more like an instruction document + type detection. Show the developer what the return value of this function is for different arguments.

//TS can overload only functions defined using the function declaration

// Code:
function handleData(x: number[]) :string// Receive onenumberArray that returns a value of type stringfunction handleData(x: string, y: number) :string// If two arguments are passed, they are added together, returning a string valuefunction handleData(x: any, y? : any) :any {         // A function that handles all of the above
  if(typeof x === 'number'&& y ! =undefined) {
    return x + y;
  }
  return x.toString();
}
handleData([1.2.3.4.5.6])
handleData('hello: '.2019)
Copy the code

TS function overloading is divided into two parts: 1. Multiple function declarations; 2. A function that handles all function declarations

1.Multiple function declarations of the same function... Defines different parameters and return valuesfunction handleData(x: number[]) :string 
function handleData(x: string, y: number) :string2. This function is all function declarations (👆). Internally, different function declarations are handled by judging different parametersfunction handleData(x: any, y? :any) :any {
  if(typeof x === 'number'&& y ! =undefined) {
    return x + y;
  }
  returnx.toString(); } In simple terms, write out each specific function declaration and finally implement a function that can handle all parameter types// The above code is compiled into JS code
function handleData(x, y) {
    if (typeof x === 'number'&& y ! =undefined) {
        return x + y;
    }
    returnx.toString(); } you will find only multiple function declarations removed. The other is the same as using JS emulation to implement overloading. -------------------------------------------------------------------------------------------------------------------- But TS overloading is not trivial, it looks like a few more lines of inconsequential code. But it can provide more stringent code checks on handleData('s') // Error passes an argument that matches the first function declaration and says 's' cannot be assigned to number[]
handleData('s'.'s') // error matches the second function declaration, indicating that 's' cannot be assigned to type numberAs stated at the beginning, TypeScript overloading is more like documentation. In order to show developers, developers know how to call.Copy the code