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