TypeScript Advanced Usage Comments

introduction

As a strong static type checking tool, now in many large-scale applications as well as in the popular JS libraries could see TypeScript figure. JS as a weakly typed language, we are in the process of writing code in a little inattentive type will modify variables out, which led to some unexpected run-time error. However TypeScript during compilation will help us solve this problem, not only the introduction of strong type checking in JS, JS and compiled code that can run on any 3 (version browser environment, Node environment and any support ECMAScript or higher ) the JS engine. Recently the company just ready to use TypeScript reconstruction of the existing system, previously used TypeScript opportunity not many, especially some useful advanced usage, so through this opportunity to re-consolidate consolidate knowledge about this area, if the wrong place, please also indicate.

1, class inheritance

In ES5, we generally by a function or a prototype based inheritance to encapsulate some of the component parts to facilitate common multiplexing, however TypeScript, we can be like a Java-like language to object-oriented manner using class inheritance to create reusable components. We can classcreate a class keyword, and based on its use newoperator to instantiate an object. To the public portion of the plurality of abstract classes, we can create a subclass of a parent class and let through extendsto inherit the parent class keyword, thus reducing redundancy write some code to increase code reusability and maintainability. Examples are as follows:

class Parent {
    readonly x: number;
    constructor() {
        this.x = 1;
    }
    
    print() {
        console.log(this.x);
    }
}

class Child extends Parent {
    readonly y: number;
    constructor() {
        // 注意此处必须优先调用super()方法
        super();
        this.y = 2;
    }
    
    print() {
        // 通过super调用父类原型上的方法,但是方法中的this指向的是子类的实例
        super.print();
        console.log(this.y);
    }
}

const child = new Child();
console.log(child.print()) // -> 1 2

In the above example, Childsubclasses of the parent class in the printmethod of overwriting, while internal super.print()to invoke a common logical parent, enabling multiplexing logic. classKeyword as syntactic sugar constructor compiled after a TypeScript will eventually be converted into a good compatibility ES5 browser recognizes the code. classVery common in object-oriented programming paradigm, so in order to clarify the implementation mechanism behind it, we might as well take the time to look at the compiled after conversion code look like (of course, already familiar with this part of the students can directly jump over).

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    }
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var Parent = /** @class */ (function () {
    function Parent() {
        this.x = 1;
    }
    Parent.prototype.print = function () {
        console.log(this.x);
    };
    return Parent;
}());
var Child = /** @class */ (function (_super) {
    __extends(Child, _super);
    function Child() {
        var _this = 
        // 注意此处必须优先调用super()方法
        _super.call(this) || this;
        _this.y = 2;
        return _this;
    }
    Child.prototype.print = function () {
        // 通过super调用父类原型上的方法,但是方法中的this指向的是子类的实例
        _super.prototype.print.call(this);
        console.log(this.y);
    };
    return Child;
}(Parent));
var child = new Child();
console.log(child.print()); // -> 1 2

These are the complete code conversion, in order to facilitate comparison, here the original annotation information retention, careful study of this code we will find the following points:
1) the subclass Childconstructor super()method is converted into var _this = _super.call(this) || this, here's _superfinger is the parent class Parent, so the meaning of this code is to call the parent class constructor and thisbind to the instance of a subclass, so subclass instances can have a parent class xproperty. Therefore, in order to achieve property inheritance, we must call in a subclass constructor super()method, if you do not call the compiler will not pass.

2) sub-class Childof printmethods super.print()method is converted into _super.prototype.print.call(this)the meaning of this code is to call on the parent class prototype printmethods and methods thisto sub-class instance, as in the previous step, we have to inherit the parent class xattributes , so here we will directly print out the sub-class instance xattribute.

3) extendsthe keyword is converted into a final __extends(Child, _super)process in which _superrefers to a parent class Parent, for easy viewing, where the _extendsmethods proposed to separate study.

var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
        return extendStatics(d, b);
    }
    return function (d, b) {
        // 第一部分
        extendStatics(d, b);
        
        // 第二部分
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();

In the above code, can be divided into two parts to be understood that the first part of extendStatics(d, b)the method, the latter method of the second portion for two lines of code.

第一部分

In the extendStaticsinternal method, although a relatively large amount of code, but not difficult to find but it is still primarily for compatibility with version ES5 execution environment. In ES6 in a new Object.setPrototypeOfmethod for prototype manually set the target, but in general we ES5 environment through a non-standard __proto__to set property Object.setPrototypeOfmethod is actually a set of principles by which the object properties of the prototype, its implementation as follows:

Object.setPrototypeOf = function(obj, proto) {
    obj.__proto__ = proto;
    return obj;
}

In the extendStatics(d, b)method, drefers to the subclass Child, brefers to a parent class Parent, so the effect of the method can be interpreted as:

// 将子类Child的__proto__属性指向父类Parent
Child.__proto__ = Parent;

These lines of code can be understood as the inherited constructor, inheritance or call static properties and static methods, properties, and methods that is not mounted to the constructor's prototypeprototype, but mounted directly to the constructor itself, because in JS the function itself can be used as a target, and can be given any other attributes, for example:

function Foo() {
  this.x = 1;
  this.y = 2;
}

Foo.bar = function() {
  console.log(3);
}

Foo.baz = 4;
console.log(Foo.bar()) // -> 3
console.log(Foo.baz) // -> 4

So when we subclasses Childto Child.somePropertyaccess the property, if the child does not exist in the class will be by Child.__proto__looking for the same name property of the parent class path in this way to achieve the static properties and static methods of search.

第二部分

In the second part contains only the following two lines of code:

function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());

Wherein the dmeans subclass Child, brefers to the parent class Parent, there are several ways to achieve JS inheritance familiar students can see at a glance, as used herein, parasitic inheritance modular manner, by borrowing an intermediate function __()to avoid when modified subclass prototypethe parent class method when the prototypeimpact caused. We know that, after an object is instantiated by the constructor in JS, the object has an __proto__attribute and its constructor point prototypeattributes, for example:

function Foo() {
  this.x = 1;
  this.y = 2;
}

const foo = new Foo();
foo.__proto__ === Foo.prototype; // -> true

For this embodiment, if by subclassing Childafter to instantiate an object, will produce the following associations:

const child = new Child();
child.__proto__ === (Child.prototype = new __());
child.__proto__.__proto__ === __.prototype === Parent.prototype; 

// 上述代码等价于下面这种方式
Child.prototype.__proto__ === Parent.prototype;

So when we subclass Childinstance childthrough object child.someMethod()when a method is invoked, if the method does not exist in the instance, it will along the __proto__upward look to continue, eventually will go through the parent class Parent's prototypeprototype to live out this way inherited methods.

Based on an analysis of more than two parts, we can conclude the following two points:

// 表示构造函数的继承,或者叫做静态属性和静态方法的继承,总是指向父类
1. Child.__proto__ === Parent;

// 表示方法的继承,总是指向父类的prototype属性
2. Child.prototype.__proto__ === Parent.prototype;

2, access modifier

TypeScript provides access modifier for us (Access Modifiers) to limit the classexternal access to internal property, access modifiers mainly includes the following three:

  • public: Public modifier, it modifies the properties and methods are public and can be accessed from anywhere, all the default properties and methods in the case are publicin.
  • private: Private modifier, it modifies the properties and methods classare not visible externally.
  • protected: Protected modifiers, and privatesimilar, but it modifies the properties and methods within the subclass is allowed to access .

Let's compare several modifier by some examples:

class Human {
    public name: string;
    public age: number;
    public constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

const man = new Human('tom', 20);
console.log(man.name, man.age); // -> tom 20
man.age = 21;
console.log(man.age); // -> 21

In the above example, since we set the access modifiers public, by way of example we manaccess nameand ageproperties are allowed, while the ageproperty reassignment is allowed. However, in some cases, we hope that some properties are not visible outside, but can not be modified, then we can use privatemodifiers:

class Human {
    public name: string;
    private age: number; // 此处修改为使用private修饰符
    public constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

const man = new Human('tom', 20);
console.log(man.name); // -> tom
console.log(man.age);
// -> Property 'age' is private and only accessible within class 'Human'.

We will agemodifier modifies the properties privatelater, by external man.ageaccess it, TypeScript will find at compile time, which is a private property and ultimately will be thrown.

Note: do not restrict access to your private property after TypeScript code compilation.

The compiled code is as follows:

var Human = /** @class */ (function () {
    function Human(name, age) {
        this.name = name;
        this.age = age;
    }
    return Human;
}());
var man = new Human('tom', 20);
console.log(man.name); // -> tom
console.log(man.age); // -> 20

Use privatemodifier modified property or method is not allowed access in a subclass of , for example:

class Human {
    public name: string;
    private age: number;
    public constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

class Woman extends Human {
    private gender: number = 0;
    public constructor(name: string, age: number) {
        super(name, age);
        console.log(this.age);
    }
}

const woman = new Woman('Alice', 18);
// -> Property 'age' is private and only accessible within class 'Human'.

In the above example because the parent class Humanin the ageproperty is set private, so the subclasses Womancan not have access to the ageproperty, in order to allow to allow access in a subclass ageproperty, we can use protectedmodifiers to be modified:

class Human {
    public name: string;
    protected age: number; // 此处修改为使用protected修饰符
    public constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

class Woman extends Human {
    private gender: number = 0;
    public constructor(name: string, age: number) {
        super(name, age);
        console.log(this.age);
    }
}

const woman = new Woman('Alice', 18); // -> 18

When we privatetime modifier for the constructor, then the class is not allowed to be inherited or instantiated , for example:

class Human {
    public name: string;
    public age: number;
    
    // 此处修改为使用private修饰符
    private constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

class Woman extends Human {
    private gender: number = 0;
    public constructor(name: string, age: number) {
        super(name, age);
    }
}

const man = new Human('Alice', 18);
// -> Cannot extend a class 'Human'. Class constructor is marked as private.
// -> Constructor of class 'Human' is private and only accessible within the class declaration.

When we protectedtime modifier is used constructor, the class represents only be inherited , for example:

class Human {
    public name: string;
    public age: number;
    
    // 此处修改为使用protected修饰符
    protected constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

class Woman extends Human {
    private gender: number = 0;
    public constructor(name: string, age: number) {
        super(name, age);
    }
}

const man = new Human('Alice', 18);
// -> Constructor of class 'Human' is protected and only accessible within the class declaration.

In addition, we also can directly into the parameter modifier constructor, for example:

class Human {
    // public name: string;
    // private age: number;
    
    public constructor(public name: string, private age: number) {
        this.name = name;
        this.age = age;
    }
}

const man = new Human('tom', 20);
console.log(man.name); // -> tom
console.log(man.age);
// -> Property 'age' is private and only accessible within class 'Human'.

3, interface constructor signature

When we have a lot of projects in different classes and there are certain aspects in common between these classes may, in order to describe this common ground, we can extract it to an interface (interface) for centralized maintenance, and use implementskeyword to implement this interface, for example:

interface IHuman {
    name: string;
    age: number;
    walk(): void;
}

class Human implements IHuman {
    
    public constructor(public name: string, public age: number) {
        this.name = name;
        this.age = age;
    }

    walk(): void {
        console.log('I am walking...');
    }
}

The code at compile time can pass, but we noticed Humanthat contains the class constructorconstructor, and if we want to make a signature for the constructor function defined in an interface Humanclass that implements this interface, see what happens:

interface HumanConstructor {
  new (name: string, age: number);    
}

class Human implements HumanConstructor {
    
    public constructor(public name: string, public age: number) {
        this.name = name;
        this.age = age;
    }

    walk(): void {
        console.log('I am walking...');
    }
}
// -> Class 'Human' incorrectly implements interface 'HumanConstructor'.
// -> Type 'Human' provides no match for the signature 'new (name: string, age: number): any'.

However TypeScript will compile error, tell us incorrectly implements HumanConstructorthe interface. This is because when a class implements an interface, the only part of the instance compiles inspection, the static part of the class will not be checking compiler. So here we attempt static portion Stated another way, the direct operation type, for example:

interface HumanConstructor {
  new (name: string, age: number);    
}

interface IHuman {
    name: string;
    age: number;
    walk(): void;
}

class Human implements IHuman {
    
    public constructor(public name: string, public age: number) {
        this.name = name;
        this.age = age;
    }

    walk(): void {
        console.log('I am walking...');
    }
}

// 定义一个工厂方法
function createHuman(constructor: HumanConstructor, name: string, age: number): IHuman {
    return new constructor(name, age);
}

const man = createHuman(Human, 'tom', 18);
console.log(man.name, man.age); // -> tom 18

In the above example to create a factory by an additional method createHumanand constructor as the first argument, at this time when we call createHuman(Human, 'tom', 18)the compiler will check whether the first argument HumanConstructorconstructor interface signature.

4, Statement merger

In a statement the merger is the most common type of consolidation is the interface, so start here, Interface began to introduce some of the more common way merger.

4.1 Interface merger

Sample code is as follows:

interface A {
    name: string;
}

interface A {
    age: number;
}

// 等价于
interface A {
    name: string;
    age: number;
}

const a: A = {name: 'tom', age: 18};

The combined interfaces easier to understand manner, i.e., a plurality of the same name interface declaration, each interface contains different property declarations, eventually the property declarations from the plurality of interfaces are merged into a single interface.

Note: Non-function interface all members of the same name must be unique, if not unique it must be the same type of guarantee, otherwise the compiler will complain. For the function member, after the declaration of the same name will be overwritten with the same name Interface Interface declared before, an interface that is a function of the same name after the statement is equivalent to a heavy load, it has a higher priority.

4.2 Merge function

The combined function can be understood as a simple function of the overload, i.e., by simultaneously achieved function of the same name defined in a plurality of parameters of different types or different type of return value, the following sample code:

// 函数定义
function foo(x: number): number;
function foo(x: string): string;

// 函数具体实现
function foo(x: number | string): number | string {
    if (typeof x === 'number') {
        return (x).toFixed(2);
    }
    
    return x.substring(0, x.length - 1);
}

In the example above, we fooproceed defined function multiple times, each different type defined function parameters, return value different types, as a function of the last of the specific implementation, the implementation only compatible to all previously defined, the compiler He does not complain.

Note: TypeScript compiler will take precedence match from the beginning of the function definition, so if more than one function is defined inclusion relationship, you need to put the most accurate function definition front, otherwise it will not always be matched.

4.3 type alias joint

Interface type alias in conjunction with the merger differ, the type of alias will not create a new type, just create a new alias to make reference to more than one type, but can not be the same as the interface 实现(implements)and 继承(extends), for example:

type HumanProperty = {
    name: string;
    age: number;
    gender: number;
};

type HumanBehavior = {
    eat(): void;
    walk(): void;
}

type Human = HumanProperty & HumanBehavior;

let woman: Human = {
    name: 'tom',
    age: 18,
    gender: 0,
    eat() {
        console.log('I can eat.');
    },
    walk() {
        console.log('I can walk.');
    }
}

class HumanComponent extends Human {
    constructor(public name: string, public age: number, public gender: number) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    
    eat() {
        console.log('I can eat.');
    }
    
    walk() {
        console.log('I can walk.');
    }
}
// -> 'Human' only refers to a type, but is being used as a value here.

5, keyof index query

In the TypeScript keyofsomewhat similar to the JS Object.keys()method, but the difference is that the former is traversed in the character string type index, which is the object traversed by keys, for example:

interface Rectangle {
    x: number;
    y: number;
    width: number;
    height: number;
}

type keys = keyof Rectangle;
// 等价于
type keys = "x" | "y" | "width" | "height";

// 这里使用了泛型,强制要求第二个参数的参数名必须包含在第一个参数的所有字符串索引中
function getRectProperty<T extends object, K extends keyof T>(rect: T, property: K): T[K] {
    return rect[property];
} 

let rect: Rectangle = {
    x: 50,
    y: 50,
    width: 100,
    height: 200
};

console.log(getRectProperty(rect, 'width')); // -> 100
console.log(getRectProperty(rect, 'notExist'));
// -> Argument of type '"notExist"' is not assignable to parameter of type '"width" | "x" | "y" | "height"'.

In the above example by using us keyofto limit the parameters of the function names propertymust be included in the types Rectangleof all the strings in the index, if not containing the compiler error, attribute names may be used to detect whether an object is written at compile time error .

6, Partial optional attributes

In some cases, we hope that all property types are not necessary, exist only under certain conditions, we can use Partialall the attributes identifies the type that has been declared in is optional, for example:

// 该类型已内置在TypeScript中
type Partial<T> = {
    [P in keyof T]?: T[P]
};

interface Rectangle {
    x: number;
    y: number;
    width: number;
    height: number;
}

type PartialRectangle = Partial<Rectangle>;
// 等价于
type PartialRectangle = {
    x?: number;
    y?: number;
    width?: number;
    height?: number;
}

let rect: PartialRectangle = {
    width: 100,
    height: 200
};

Since we use in the example above Partialall the attributes identified as optional, so the final rectobject, although only contain widthand heightproperty, but the compiler is still not being given, when we can not clearly determine which properties contained in the object, we can pass Partialdeclared.

7, Pick partial selection

In some application scenarios, we may need to extract from a type declared in a sub-type, comprising a supertype of some or all of the subtype attribute, then we can use Pickto implement, the following sample code:

// 该类型已内置在TypeScript中
type Pick<T, K extends keyof T> = {
    [P in K]: T[P]
};

interface User {
    id: number;
    name: string;
    age: number;
    gender: number;
    email: string;
}

type PickUser = Pick<User, "id" | "name" | "gender">;
// 等价于
type PickUser = {
    id: number;
    name: string;
    gender: number;
};

let user: PickUser = {
    id: 1,
    name: 'tom',
    gender: 1
};

In the above example, since we only care about the userobject id, nameand genderwhether there are other attributes not clearly defined, so we can use Pickfrom Userand ignored by the compiler checks for other attributes of picking up the properties we care interface.

8, never never exist

neverRepresents the type of those values will never exist, such an exception is thrown in a function or an infinite loop, neverthe type can be any type of sub-types can be assigned to any type, but did not reverse type can be used as a nevertype of child type, for example:

// 函数抛出异常
function throwError(message: string): never {
    throw new Error(message);
}

// 函数自动推断出返回值为never类型
function reportError(message: string) {
    return throwError(message);
}

// 无限循环
function loop(): never {
    while(true) {
        console.log(1);
    }
}

// never类型可以是任何类型的子类型
let n: never;
let a: string = n;
let b: number = n;
let c: boolean = n;
let d: null = n;
let e: undefined = n;
let f: any = n;

// 任何类型都不能赋值给never类型
let a: string = '123';
let b: number = 0;
let c: boolean = true;
let d: null = null;
let e: undefined = undefined;
let f: any = [];

let n: never = a;
// -> Type 'string' is not assignable to type 'never'.

let n: never = b;
// -> Type 'number' is not assignable to type 'never'.

let n: never = c;
// -> Type 'true' is not assignable to type 'never'.

let n: never = d;
// -> Type 'null' is not assignable to type 'never'.

let n: never = e;
// -> Type 'undefined' is not assignable to type 'never'.

let n: never = f;
// -> Type 'any' is not assignable to type 'never'.

9, Exclude property to exclude

And Pickcontrast, Pickfor picking up the properties we need to be concerned, and Excludethe properties we do not care for the excluded, for example:

// 该类型已内置在TypeScript中
// 这里使用了条件类型(Conditional Type),和JS中的三目运算符效果一致
type Exclude<T, U> = T extends U ? never : T;

interface User {
    id: number;
    name: string;
    age: number;
    gender: number;
    email: string;
}

type keys = keyof User; // -> "id" | "name" | "age" | "gender" | "email"

type ExcludeUser = Exclude<keys, "age" | "email">;
// 等价于
type ExcludeUser = "id" | "name" | "gender";

In the example above us by ExcludeUserpassing we do not care of ageand emailproperty, Excludewill help us eliminate unwanted attributes, leaving the property id, nameand genderis the property that we need to be concerned. In general, Excluderarely used alone or in conjunction with other types of more complex functions to achieve more useful.

10, Omit attribute ignored

In one usage, we use Excludeto exclude other undesired properties, but the higher the degree of coupling written in the above example, when there are other types of processing is also a need, then it is necessary to again achieve the same logic, we may wish to Still further encapsulation hides the underlying details of these processes, only a simple exposure to the external public interface, for example:

// 使用Pick和Exclude组合实现
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

interface User {
    id: number;
    name: string;
    age: number;
    gender: number;
    email: string;
}

// 表示忽略掉User接口中的age和email属性
type OmitUser = Omit<User, "age" | "email">;
// 等价于
type OmitUser = {
  id: number;
  name: string;
  gender: number;
};

let user: OmitUser = {
    id: 1,
    name: 'tom',
    gender: 1
};

In the above example, we need to ignore the Userinterface ageand emailattributes, you only need to pass the interface and attribute Omitcan be for other types too, greatly improving the type of scalability, easy reuse.

to sum up

Summary In this article several TypeScript of tips, if found in our TypeScript project, there are a lot of places with a common type declaration, then it may be used in the text of several techniques to optimize it to improve and increase the code maintainable and reusability. Before the author does not have many opportunities to use TypeScript, so recently also learn as summary, if you have the wrong place text, also want to be able to correct me in the comments area.

communicate with

If you feel that the content of this article to help you, can help me look at the author's public number [ front-end realm ], every week trying to original number of front-end technology dry goods, the number of public attention can invite you to join front-end technology exchange group, we can communicate with each other, and common progress.

The article has been updated to synchronize Github blog , if the article can still feel, feel free to star!

One of your thumbs, so I deserve more effort!

The face of adversity to grow, and only continue to learn in order to become better themselves, and the king of mutual encouragement!

Guess you like

Origin www.cnblogs.com/tangshiwei/p/12052494.html