Interface, class and generic type of “quick start typescript”

Time:2021-11-24
[TOC]

1、 Interface

1. Definition of interface

In other languages, such as C + +, Java, Python and other language interfaces, we will spend a lot of time learning, but in TS, we don’t need to spend too much time on the interface. It is not as difficult to understand and use as the interface in the above language. When learning the interface in TS, you can think of “this interface is not the other interface”. In short, the class in TS is used to describe a type

Let’s take a look at the definitions in the code:

Keywords are required to define interfaces:interface

//Define interface type
interface Employee {
  name: string,
  salary: number
}

This is a simple interface

Let’s see how to use this interface!

const em1:Employee =  {
    name:'jhon',
    salary: 123,
}

const em2: Employee = {
    name: 'ice_moss',
    salary: 8000,
}

console.log(em2, em1)

Output results:

{
  "name": "ice_moss",
  "salary": 8000,
},  {
  "name": "jhon",
  "salary": 12000
} 

We define an interface employee, and then define variables EM1 and em2. Their types are employee. We use EM1 and em2 to implement employee’s properties and methods. We call EM1 and em2 to implement employee’s interface

2. Interface syntax

In the above example, we only describe the properties in the interface. Next, we implement the properties and methods together:

//Define interface type
interface Employee {
    name: string   
    salary: number
    bonus?: number
    getIncom():number  
}

const em1:Employee =  {
    name:'jhon',
    salary: 12000,
    bonus: 2000,
    getIncom(){
        return this.salary
    }
}

const em2: Employee = {
    name: 'ice_moss',
    salary: 8000,
    bonus: 3000,
    getIncom(){
        return this.salary + (this.bonus || 0)
    }
}
3. Advanced usage of interface
3.1 optional parameter series
interface Employee {
  name?: {// optional parameters
    first?:  String // optional parameters
    last: string
  }
  salary: number
  bonus?: number
}

//Find optional parameters
function hasBadName(e: Emplotee){
  if(name){
    if(name.first){
      name.fisrt.startWith('AAA')
    }
  }
}

It looks more complicated. We have a simple method:

function hasBadName(e: Employee){
    return e.name?.first?.startsWith('AAA')
}
//Explanation: the first step is to judge whether e.name exists. If it does not exist, it returns undefined; If e.name exists, continue to judge downward in this way

Let’s look at the specific usage:

interface Employee {
    name?: {
        last?: string,
        first?: string,
    }   
    salary: number
    bonus?: number        
}

function hasBadName(e: Employee){
    return e.name?.first?.startsWith('AAA')
}

console.log(hasBadName({
    name:{
        first: 'jhon',
        last: 'smith',
    },
    salary: 8000,
}))

//Output: false

console.log(hasBadName({
    name:{
        last: 'smith',
    },
    salary: 8000,
}))

//Output: undefined

So we can protect our program from hanging up

3.2 non null assertion

In optional parameter concatenation, we can’t solve the problem fundamentally, so we need to useNon null assertion

You only need to?Give for!Just.

3.3 interface expansion

We can understand it as inheritance in object-oriented language. We can understand the old interface as the father interface, and the new interface as the son interface. The son interface is through keywordsextendsInherit the properties and methods of the parent interface

example:

//Son interface
Interface employee extensions hasname {// interface extension: Extensions
    salary: number
    bonus?: number       
}

//Father interface
interface HasName{
    name: {
        last: string,
        first: string,
    } 
}

//EM1 implements the interface employee
const em1: Employee = {
    name:{
        first:'_moss',
        last: 'ice'
    },
    salary: 20000,
    bonus: 1000
}

console.log(em1)

Output results:

{
  "name": {
    "first": "_moss",
    "last": "ice"
  },
  "salary": 20000,
  "bonus": 1000
} 
3.4 interface and type assertion
3.4.1 interface

The “union” here is actually more like the “intersection” in our mathematics

//Interface and type assertions
//Suppose this is a button
interface WxButton{
    visible: boolean,
    enabled:boolean,
    onClick():void,
}

//Suppose this is a picture
interface WxImage{
    visible: boolean,
    src: string,
    withd:number,
    heigth: number,
}

It can be seen that the “and” here actually takes the public parts of the two interface types

Interface, class and generic type of

3.4.2 type assertion
//Interface and type assertions
interface WxButton{
    visible: boolean,
    enabled:boolean,
    onClick():void,
}

interface WxImage{
    visible: boolean,
    src: string,
    withd:number,
    heigth: number,
}

//Type assertion
function processElement(e: WxButton | WxImage){
    if((e as any).onClick){
        const btn = e as WxButton
        btn.onClick()
    }else{
        const img = e as WxImage
        console.log(img.src)
    }
}


processElement({
    visible: true,
    src: 'this is src',
    withd: 20,
    heigth: 30,
})

processElement({
    visible: true,
    enabled: true,
    onClick(){
        console.log('onClick')
    }
})

//Output results:
//  "this is src" 
//  "onClick"

2、 Class

1. Definition of class

Class describes the common properties and methods of the created object.

Typescript supports all object-oriented features, such as classes, interfaces, and so on.

The typescript class is defined as follows:

class class_name {
  //Scope
}

Class has three important modules:

  1. Properties:

    A field is a variable declared by a class.

  2. Constructor:

    Called when a class is instantiated, memory can be allocated for the object of the class.

  3. method:

    Method is the operation to be performed by the object.

1.1 properties (fields) of class
class Employee {
  name: string = 'ice_ Moss' // note that class fields need to be initialized here
  salary: number  = 20000
}
1.2 constructor

We declare oneEmployeeL class. The constructor initializes the field name and salary of the class in the instance. The constructor uses keywords during initializationthis: indicates the fields under this class

class Employee {
  name: string = 'ice_moss' 
  salary: number  = 20000
  constructor(name: string, salary: number){
    this.name = name
    this.salary = salary
  }
}

//Of course, we can use the keyword public in the constructor, and then we don't have to declare fields separately:
class Employee {
  constructor(public name: string, public salary: number){
    this.name = name
    this.salary = salary
  }
//It will look more concise
//In fact, it can also be written directly as:
  class Employee {
  constructor(public name: string, public salary: number){}
1.3 method
class Employee {
  constructor(public name: string, public salary: number){
    this.name = name
    this.salary = salary
  }
  Getname(): void {// get name
      console.log(this.name)
  }
  Getsalary(): void {// get salary
      console.log(this.salary)
  }
}
2. Class instantiation
var object_name = new class_name([ arguments ])

It is still the Employee class

class Employee {
  constructor(public name: string, public salary: number){
    this.name = name
    this.salary = salary
  }
  Getname(): void {// get name
      console.log(this.name)
  }
  Getsalary(): void {// get salary
      console.log(this.salary)
  }
}

//Class, you need to use the keyword "new"
const em = new Employee('jhon', 9000)
console.log(em)
em.getName()
em.getSalary()


//Output results:
Employee: {
  "name": "jhon",
  "salary": 9000
} 
 "jhon" 
 9000
3. Access control modifier

In typescript, access control characters can be used to protect access to classes, variables, methods, and constructor methods. Typescript supports 3 different access permissions.

Public (default): public, can be accessed anywhere.

protected: protected and accessible by itself and its subclasses and parents.

private: private and can only be accessed by the class where it is defined.

Let’s continue to look at examples:

class Employee{
    private allocatebonus?:  Number // allocatebonus is a private attribute and an optional parameter
    private bonus: number = 0
    constructor(public name:string, public salary:number){
        this.name = name
        this.salary = salary
    }

! [screenshot 7.08.59 PM 2021-11-21] (/ users / Feng / library / Application Support / typora user images / screenshot 7.08.59 PM 2021-11-21. PNG)

At this time, we cannot access allocatebonus and bonus in the instance

3. getter/setter

We can write the function in the form of calling field in the class

class Employee{
    private allocatebonus?: number
    constructor(public name:string, public salary:number){
    }
    set bonus(v: number){
        this.allocatebonus = v
    }
    get bonus(){
        return this.allocatebonus || 0
    }
}

Instantiation:

const em = new Employee('jhon', 9000)
em.bonus = 2000
console.log(em)

Output:

Employee: {
  "name": "jhon",
  "salary": 9000,
  "allocatebonus": 2000
} 
4. Class inheritance (method rewriting of inherited classes)

Class inheritance is similar to interface expansion. We can understand it as inheritance in object-oriented language. The old class is called the parent class, and the new class is called the subclass. The subclass passes through keywordsextendsInherit the properties and methods of the parent class

After class inheritance, subclasses can redefine the methods of the parent class. This process is called method rewriting.

The super keyword is a direct reference to the parent class, which can reference the properties and methods of the parent class.

//Parent class
class Employee{
    private allocatebonus?: number
    constructor(public name:string, public salary:number){
    }
    set bonus(v: number){
        this.allocatebonus = v
    }
    get bonus(){
        return this.allocatebonus || 0
    }

}

//Subclass
class Manager extends Employee{
    private reporters: Employee[]
     constructor(name:string, salary:number) {
        super(name, salary)
        this.reporters = []
     }
     addReporters(e: Employee){
         this.reporters.push(e)
     }
}

const manager = new Manager('DF', 200000)

After class inheritance is successful, we can see the following figure:

! [screenshot 11.29.34 PM, 2021-11-21] (/ users / Feng / library / Application Support / typora user images / screenshot 11.29.34 PM, 2021-11-21. PNG)

3、 Implementing interfaces with classes

After learning the contents of the previous two parts, we can now use classes to implement interfaces. Let’s take a look at the simple implementation:

1. Interface implicit implementation
//Interface
interface Emploeey{
    name: string
    salary: number
}

//Class
class Emplmpl{
    name: string
    salary: number
    Constructor (Name: string, salary: number) {// constructor
        this.name = name
        this.salary = salary
    }
}
//Here, we may not be able to directly see how the class implements the interface. In TS, as long as the class meets the attributes and methods of the interface, that is, the class implements the interface
const emplpml = new Emplmpl('ice_moss', 10000)

In this way, the interface is implemented by the class. Of course, it can also be declared here:

const emplpml = new Emplmpl('ice_moss', 10000)
const em1: Emploeey = emplpml

Here’s a summary: the above implementation is implicit. When you are asked to assign a value, the compiler will compare the fields in the interface and class one by one; But there is also a problem here. If we lack the attributes or methods in the interface in the class, we may not be able to implement the interface by using the implicitly implemented method, but the compiler will not remind us.

2. Interface display implementation

We can use classes to implement interfaces or display implementations, which can inform us of errors in the implementation process in advance

interface Emploeey{
    name: string
    salary: number
}

Class emplmpl implements employee {// display implementation of the interface
    name: string
    salary: number
    Constructor (Name: string, salary: number) {// constructor
        this.name = name
        this.salary = salary
    }
}

const emplpml = new Emplmpl('ice_moss', 10000)
3. How to select implicit implementation – display implementation

Here’s an example: we have a front-end applet. We have many services, such as pages, interfaces, etc

Definer = implementer => Display implementation

Define and implement interfaces in the servce.ts file

//Servce.ts
//Define interface
interface Servce {
    Login(): void // login
    Gettrips(): string // get the trip
    Getlic(): string // get the driver's license number
    Starttrip(): void // start travel
    Updatalic (LIC: String): void // update the driver's license number
}
//Implementation interface
class RPCservce implements Servce {
    login(): void {
        throw new Error("Method not implemented.");
    }
    getTrips(): string {
        throw new Error("Method not implemented.");
    }
    getLic(): string {
        throw new Error("Method not implemented.");
    }
    startTrip(): void {
        throw new Error("Method not implemented.");
    }
    updataLic(lic: string): void {
        throw new Error("Method not implemented.");
    }
}

Here is a login page file:

//login: Page
//file: Login.ts
const page ={
servce: new RPCservce() as Servce,
Onloginbuttonclicked() {// click login
    //Use interface
    this.servce.login()
}}

It can be seen that this is a display implementation, and we use all the methods in the interfaceclass RPCservce implements ServceImplement the interface

So this is:Definer = implementer => Display implementation

However, in TS, we prefer implicit implementation:

In our display implementation, we only need to call one method on the login page:this.servce.login()To serve

But when we call.There are many methods (services), but we don’t want to see that we need to use implicit

Implementation of:User = implementer => Implicit implementation

The definition of the interface is completed by the user. What methods (services) the user needs and then define the required interface. Let’s continue to look at the examples:

//Implementation interface
class RPCservce{
    login(): void {
        throw new Error("Method not implemented.");
    }
    getTrips(): string {
        throw new Error("Method not implemented.");
    }
    getLic(): string {
        throw new Error("Method not implemented.");
    }
    startTrip(): void {
        throw new Error("Method not implemented.");
    }
    updataLic(lic: string): void {
        throw new Error("Method not implemented.");
    }
}


interface Servcelogin {
    Login(): void // login
}

//Login: page login page
//file: Login.ts
const loginPage = {
loginServce: new RPCservce() as Servcelogin,
onLoginButtonCliked(){
    //Use interface
    this.loginServce.login()
}}


interface ServceTrips{
    Gettrips(): string // get the trip
    Starttrip(): void // start travel
}

//Trips: page trip page
//file: Trips.ts
const tripsPage = {
 tripsServce: new RPCservce() as Servce,
 tripsButtonCliked(){
     this.tripsServce.getTrips()
 }   
}

In this way, we don’t have to use the methods in the previous servce interface. Which page needs to use what method (service) and which page can define the interface. Each interface is very small, and there may be only one or two functions, which is also convenient for interface maintenance.

4、 Generics

1. Introduction

In software engineering, we should not only create consistent and well-defined APIs, but also consider reusability. Components can support not only current data types, but also future data types, which provides you with very flexible functions when creating large-scale systems.

In languages like c# and Java, you can usegeneric paradigmTo create reusable components. A component can support multiple types of data. In this way, users can select data types to use components according to their own needs.

const a: Array<String> = []
a.push('hhhh')
console.log(a)

const b: Array<number> = []
b.push(100)
console.log(b)

//Output:
["arr", "kk"] 
["hhhh"] 
[100] 

//If there are 10000 data types, do we need to write 10000 such functions, which shows that this is unrealistic, so we need generics to simplify our use:
class MyArray<T> {
    data: T[] = []
    add(t: T){
        this.data.push(t)
    }
    print(){
        console.log(this.data)
    }
}

const test = new MyArray<string>()
test.add('arr')
test.add('kk')
test.print()

//Output:
["arr", "kk"]

Let’s create the first example of using generics: the identity function. This function returns any value passed in. You can think of this function asechoCommand.

Without generics, this function may be as follows:

function identity(arg: number): number {
    return arg;
}

Or, we useanyType to define the function:

function identity(arg: any): any {
    return arg;
}

In this way, when the data types are different, we need to write many functions with the same functions and deal with different types. Here, we need to use generics. Generics are like a template, which can be used for any data type.

useanyType causes this function to receive any type ofargParameter, so some information is lost: the type passed in should be the same as the type returned. If we pass in a number, we only know that any type of value can be returned.

Therefore, we need a way to make the type of the return value the same as the type of the incoming parameter. Here, we useType variable, it is a special variable that is used only to represent types, not values.

function identity<T>(arg: T): T {
    return arg;
}

We added a type variable to identityTTHelp us capture the types passed in by users (for example:number)After that, we can use this type. Then we used it againTAs the return value type. Now we can know that the parameter type is the same as the return value type. This allows us to track the type of information used in the function.

We put this versionidentityA function is called generic because it can be applied to multiple types. Different from usingany, it doesn’t lose information, like the first example, like maintaining accuracy, passing in numeric types and returning numeric types.

After we define a generic function, we can use it in two ways. The first is to pass in all parameters, including type parameters:

let output = identity<string>("myString");  // type of output will be 'string'

Here we clearly specifyTyesstringType and passed to the function as a parameter, using<>Enclose instead of()

The second method is more common. Usedtype inference – that is, the compiler will automatically help us determine the type of T according to the passed parameters:

let output = identity("myString");  // type of output will be 'string'

Note that we don’t have to use angle brackets(<>)To explicitly pass in types; The compiler can viewmyStringAnd then putTSet to its type. Type inference helps us keep our code concise and highly readable. If the compiler cannot automatically infer the type, it can only explicitly pass in the type of t as above. This may occur in some complex cases.

2. Use generic variables

Creating images using genericsidentityFor such generic functions, the compiler requires you to use this generic type correctly in the function body. In other words, you must treat these parameters as any or all types.

Look beforeidentityexample:

function identity<T>(arg: T): T {
    return arg;
}

If we want to print out at the same timeargThe length of the. We are likely to do this:

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}

If you do, the compiler will report an error saying that we used itargof.lengthProperty, but there is no place to indicateargHas this property. Remember, these type variables represent any type, so the person using this function may pass in a number, but the number does not.lengthProperty.

Now suppose we want to operateTType instead of directlyT。 Because we operate on arrays, so.lengthProperty should exist. We can create this array like other arrays:

function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

You can understand thisloggingIdentityType of: generic functionloggingIdentity, receive type parameterTAnd parametersarg, which is an element typeTAnd returns an element of typeTArray of. If we pass in a number array, we will return a number array because at this timeTThe type of isnumber。 This allows us to use the generic variable t as part of the type rather than the entire type, increasing flexibility.

We can also implement the above example in this way:

function loggingIdentity<T>(arg: Array<T>): Array<T> {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

If you have used other languages, you may be familiar with this grammar. The next section describes how to create a custom generic imageArray<T>Same.

3. Generic type

In the previous section, we created the identity general function, which can be applied to different types. In this section, we look at the types of functions themselves and how to create generic interfaces.

The type of a generic function is no different from that of a non generic function, except that there is a type parameter in the front, like a function declaration:

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <T>(arg: T) => T = identity;

We can also use different generic parameter names, as long as they correspond in quantity and usage.

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <U>(arg: U) => U = identity;

We can also define generic functions using object literals with call signatures:

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: {<T>(arg: T): T} = identity;

This leads us to write the first generic interface. We take the object literal in the above example as an interface:

interface GenericIdentityFn {
    <T>(arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn = identity;

For a similar example, we might want to treat a generic parameter as a parameter of the entire interface. In this way, we can clearly know which generic type is used (for example:Dictionary < string > not just a dictionary)。 In this way, other members of the interface can also know the type of this parameter.

interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

Notice that our example has made a few changes. Instead of describing generic functions, non generic function signatures are used as part of generic types. When we useGenericIdentityFnWhen, you have to pass in a type parameter to specify the generic type (here:number), lock the type used in the subsequent code. For describing which types belong to the generic part, it is helpful to understand when to put parameters in the call signature and when to put parameters on the interface.

In addition to generic interfaces, we can also create generic classes. Note that generic enumerations and generic namespaces cannot be created.

Generic classes look like generic interfaces. Generic class usage(<>)Enclose the generic type, followed by the class name.

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

GenericNumberThe use of class is very intuitive, and you may have noticed that there is no limit to its usenumberType. You can also use strings or other more complex types.

let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };

console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));

Just like interfaces, putting generic types directly after classes can help us confirm that all properties of a class are using the same type.

As we said in the class section, a class has two parts: the static part and the instance part. Generic class refers to the type of instance part, so static properties of class cannot use this generic type.

Note:Generic part reference typescriptOfficial documents

This work adoptsCC agreement, reprint must indicate the author and the link to this article

Recommended Today

Seven solutions for distributed transactions

1、 What is distributed transaction Distributed transaction means that transaction participants, transaction supporting servers, resource servers and transaction managers are located on different nodes of different distributed systems. A large operation is completed by more than n small operations. These small operations are distributed on different services. For these operations, either all of them are […]