An easy to understand introduction to TypeScript

This article comprehensively introduces the common knowledge points of TypeScript, which is especially friendly for new beginners!

This article will take you step by step to learn 14 knowledge points related to the introduction of TypeScript. See the following figure for a detailed outline:

1, What is TypeScript

TypeScript is a free and open source programming language developed by Microsoft. It is a superset of JavaScript and essentially adds optional static types and class based object-oriented programming to the language.
TypeScript provides the latest and evolving JavaScript features, including those from ECMAScript 2015 and future proposals, such as asynchronous features and Decorators, to help build robust components. The following figure shows the relationship between TypeScript and ES5, ES2015, and ES2016:

1.1 difference between typescript and JavaScript


1.2 get TypeScript
The TypeScript compiler on the command line can use Node.js Package to install.
1. Install TypeScript
$ npm install -g typescript
2. Compile TypeScript file
$ tsc helloworld.ts

helloworld.ts => helloworld.js

Of course, for those who are just getting started with typescript, they can learn new syntax or features directly by using the online TypeScript Playground instead of installing typescript.

TypeScript Playground: https://www.typescriptlang.org/play/

2, TypeScript base type

2.1 Boolean type

let isDone: boolean = false;
// ES5: var isDone = false;

2.2 Number type

let count: number = 10;
// ES5: var count = 10;

String type

let name: string = "Semliker";
// ES5: var name = 'Semlinker';

2.4 Array type

let list: number[] = [1, 2, 3];
// ES5: var list = [1,2,3];

let list: Array<number> = [1, 2, 3]; // Array < number > generic syntax
// ES5: var list = [1,2,3];

2.5 Enum type
Using enumeration we can define some named constants. Enumerations can be used to express intent clearly or to create a set of differentiated use cases. TypeScript supports numeric and string based enumeration.
1. Digital enumeration

enum Direction {
  NORTH,
  SOUTH,
  EAST,
  WEST,
}

let dir: Direction = Direction.NORTH;

By default, the initial value of NORTH is 0, and the rest of the members grow automatically from 1. let me put it another way, Direction.SOUTH The value of is 1, Direction.EAST The value of is 2, Direction.WEST The value of is 3. The above enumeration sample code is compiled to generate the following code:

"use strict";
var Direction;
(function (Direction) {
  Direction[(Direction["NORTH"] = 0)] = "NORTH";
  Direction[(Direction["SOUTH"] = 1)] = "SOUTH";
  Direction[(Direction["EAST"] = 2)] = "EAST";
  Direction[(Direction["WEST"] = 3)] = "WEST";
})(Direction || (Direction = {}));
var dir = Direction.NORTH;

Of course, we can also set the initial value of NORTH, such as:

enum Direction {
  NORTH = 3,
  SOUTH,
  EAST,
  WEST,
}

2. String enumeration
In TypeScript 2.4, we are allowed to use string enumeration. In a string enumeration, each member must be initialized with a string literal or another string enumeration member.

enum Direction {
  NORTH = "NORTH",
  SOUTH = "SOUTH",
  EAST = "EAST",
  WEST = "WEST",
}

The ES5 codes for the above codes are as follows:

"use strict";
var Direction;
(function (Direction) {
    Direction["NORTH"] = "NORTH";
    Direction["SOUTH"] = "SOUTH";
    Direction["EAST"] = "EAST";
    Direction["WEST"] = "WEST";
})(Direction || (Direction = {}));

3. Heterogeneous enumeration
The member values of heterogeneous enumerations are a mixture of numbers and Strings:

```cpp
enum Enum {
  A,
  B,
  C = "C",
  D = "D",
  E = 8,
  F,
}
//The ES5 codes for the above codes are as follows:

```cpp
"use strict";
var Enum;
(function (Enum) {
    Enum[Enum["A"] = 0] = "A";
    Enum[Enum["B"] = 1] = "B";
    Enum["C"] = "C";
    Enum["D"] = "D";
    Enum[Enum["E"] = 8] = "E";
    Enum[Enum["F"] = 9] = "F";
})(Enum || (Enum = {}));

By observing the ES5 code generated above, we can find that there is more "reverse mapping" between numerical enumeration and string enumeration:

console.log(Enum.A) //Output: 0
console.log(Enum[0]) // Output: A

2.6 Any type
In TypeScript, any type can be classified as any type. This makes any type the top-level type of the type system (also known as the global super type).

let notSure: any = 666;
notSure = "Semlinker";
notSure = false;

Any type is essentially a escape capsule of type system. As a developer, this gives us a lot of freedom: TypeScript allows us to perform any operation on any type of value without any form of checking in advance. For example:

let value: any;

value.foo.bar; // OK
value.trim(); // OK
value(); // OK
new value(); // OK
value[0][1]; // OK

In many scenarios, this is too loose. With any type, it's easy to write code that's of the right type but has problems at run time. If we use any type, we can't use a lot of protection mechanisms provided by TypeScript. To solve the problem of any, TypeScript 3.0 introduces unknown type.

2.7 Unknown type
Just as all types can be assigned to any, all types can also be assigned to unknown. This makes unknown another top-level type of TypeScript type system (the other is any). Let's take a look at an example of using the unknown type:

let value: unknown;

value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = Math.random; // OK
value = null; // OK
value = undefined; // OK
value = new TypeError(); // OK
value = Symbol("type"); // OK

All assignments to value variables are considered to be of the correct type. But what happens when we try to assign a value of type unknown to a variable of another type?

let value: unknown;

let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error

Unknown type can only be assigned to any type and unknown type itself. Intuitively, this makes sense: only containers that can hold values of any type can hold values of unknown type. After all, we don't know what type of value is stored in the variable value.
Now let's see what happens when we try to do something with a value of type unknown. Here is the same operation we saw in the previous any chapter:

let value: unknown;

value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error

When the value variable type is set to unknown, these operations are no longer considered to be of the correct type. By changing any type to unknown, we have changed the default setting that allows all changes to prohibit any changes.

2.8 Tuple type
As we all know, arrays are generally composed of values of the same type, but sometimes we need to store values of different types in a single variable, so we can use tuples. There is no tuple in JavaScript. Tuple is a unique type in TypeScript. It works like an array.
Tuples can be used to define types with a limited number of unnamed properties. Each property has an associated type. When using tuples, you must provide a value for each property. In order to more intuitively understand the concept of metagroups, let's take a specific example:

let tupleType: [string, boolean];
tupleType = ["Semlinker", true];

In the above code, we define a variable named tupleType, whose type is an array of types [string, boolean], and then we initialize the tupleType variable according to the correct type. As with arrays, we can access elements in tuples through subscripts:

console.log(tupleType[0]); // Semlinker
console.log(tupleType[1]); // true

During tuple initialization, if there is a type mismatch, for example:

tupleType = [true, "Semlinker"];

At this point, the TypeScript compiler prompts for the following error message:

[0]: Type 'true' is not assignable to type 'string'.
[1]: Type 'string' is not assignable to type 'boolean'.

It's obviously caused by a type mismatch. When initializing tuples, we must also provide the value of each attribute, or there will be errors, such as:

tupleType = ["Semlinker"];

At this point, the TypeScript compiler prompts for the following error message:

Property '1' is missing in type '[string]' but required in type '[string, boolean]'.

2.9 Void type
To some extent, the void type seems to be the opposite of any type, which means there is no type. When a function has no return value, you will usually see that its return value type is void:

// Declare function return value as void
function warnUser(): void {
  console.log("This is my warning message");
}

The ES5 code generated by the above code compilation is as follows:

"use strict";
function warnUser() {
  console.log("This is my warning message");
}

It should be noted that declaring a variable of void type has no effect because its value can only be undefined or null:

let unusable: void = undefined;

2.10 Null and Undefined types
In TypeScript, undefined and null have their own types: undefined and null.

let u: undefined = undefined;
let n: null = null;

By default, null and undefined are subtypes of all types. That is, you can assign null and undefined to variables of type number. However, if you specify the - strict nullchecks flag, null and undefined can only be assigned to void and their respective types.
2.11 Never type
The never type represents the type of values that never exist. For example, the never type is the return value type of a function expression or arrow function expression that always throws an exception or has no return value at all.

// Function returning never must have an unreachable end point
function error(message: string): never {
  throw new Error(message);
}

function infiniteLoop(): never {
  while (true) {}
}

In TypeScript, you can use the feature of never type to realize comprehensive check. The specific examples are as follows:

type Foo = string | number;

function controlFlowAnalysisWithNever(foo: Foo) {
  if (typeof foo === "string") {
    // Here foo is narrowed to string type
  } else if (typeof foo === "number") {
    // Here foo is narrowed down to number type
  } else {
    // foo is never here
    const check: never = foo;
  }
}

Note that in the else branch, we assign the foo narrowed to never to a never variable that displays the declaration. If all the logic is correct, it should be able to compile. But if one day your colleague changes the foo type:

type Foo = string | number | boolean;

However, he forgot to modify the control process in the controlFlowAnalysisWithNever method at the same time. At this time, the foo type of else branch will be narrowed to boolean type, resulting in the inability to assign to the never type, and then a compilation error will occur. In this way, we can ensure that
The controlFlowAnalysisWithNever method always exhausts all possible types of Foo. Through this example, we can draw a conclusion: using never to avoid new union types without corresponding implementation is to write code with absolute type security.

3, TypeScript assertion

Sometimes you have situations where you know more about a value than TypeScript. Usually this happens when you know clearly that an entity has a more exact type than its existing type.
Type assertions tell the compiler, "trust me, I know what I'm doing.". Type assertions are like type conversions in other languages, but do not have special data checking and deconstruction. It has no run-time impact, only works during the compilation phase.
There are two forms of type assertion:
3.1 "angle bracket" syntax

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

3.2 as syntax

let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

4, Type guard

A type guard is some expression that performs a runtime check that guarantees the type in some scope. - TypeScript Official documents

Type protection is an expression that performs runtime checks to ensure that the type is within a certain range. In other words, type protection ensures that a string is a string, although its value can also be a number. The main idea of type protection is to try to detect attributes, methods or prototypes to determine how to handle values. At present, there are mainly four ways to achieve type protection:

4.1 in keyword

interface Admin {
  name: string;
  privileges: string[];
}

interface Employee {
  name: string;
  startDate: Date;
}

type UnknownEmployee = Employee | Admin;

function printEmployeeInformation(emp: UnknownEmployee) {
  console.log("Name: " + emp.name);
  if ("privileges" in emp) {
    console.log("Privileges: " + emp.privileges);
  }
  if ("startDate" in emp) {
    console.log("Start Date: " + emp.startDate);
  }
}

4.2 type of keyword

function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
      return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
      return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

Typeof type protection only supports two forms: typeof v = = = "typename" and typeof V! = = typename, "typename" must be "number", "string", "boolean" or "symbol". But TypeScript doesn't prevent you from comparing with other strings, and languages don't recognize those expressions as type protected.

4.3 instanceof keyword

interface Padder {
  getPaddingString(): string;
}

class SpaceRepeatingPadder implements Padder {
  constructor(private numSpaces: number) {}
  getPaddingString() {
    return Array(this.numSpaces + 1).join(" ");
  }
}

class StringPadder implements Padder {
  constructor(private value: string) {}
  getPaddingString() {
    return this.value;
  }
}

let padder: Padder = new SpaceRepeatingPadder(6);

if (padder instanceof SpaceRepeatingPadder) {
  // The type of pader is narrowed to 'spacerepeatingpader'
}

4.4 type predicates for custom type protection

function isNumber(x: any): x is number {
  return typeof x === "number";
}

function isString(x: any): x is string {
  return typeof x === "string";
}

5, Union type and type alias

5.1 joint type
Union types are usually used with null or undefined:

const sayHello = (name: string | undefined) => {
  /* ... */
};

For example, the type of name here is string | undefined, which means that the value of string or undefined can be passed to the sayHello function.

sayHello("Semlinker");
sayHello(undefined);

With this example, you can intuitively know that type A and type B combined are types that accept both a and b values.

5.2 identifiable joint

TypeScript distinguished unions, also known as algebraic data types or label union types. It contains three main points: recognizable, joint type and type guard.
The essence of this type is a kind of type protection method combining joint type and literal type. If a type is a joint type of multiple types, and multiple types contain a common attribute, you can use this common attribute to create different types of protection blocks.

1. Identifiable
Identifiability requires that each element in the union type contains a singleton type attribute, such as:

enum CarTransmission {
  Automatic = 200,
  Manual = 300
}

interface Motorcycle {
  vType: "motorcycle"; // discriminant
  make: number; // year
}

interface Car {
  vType: "car"; // discriminant
  transmission: CarTransmission
}

interface Truck {
  vType: "truck"; // discriminant
  capacity: number; // in tons
}

In the above code, we define three interfaces: Motorcycle, Car and Truck. In these interfaces, there is a vType attribute, which is called recognizable attribute, while other attributes are only related to the attribute interface.

2. Joint type
Based on the three interfaces defined above, we can create a Vehicle joint type:

type Vehicle = Motorcycle | Car | Truck;

Now we can start to use Vehicle joint type. For Vehicle type variables, it can represent different types of vehicles.

3. Type guard
Let's define an evaluatePrice method, which is used to calculate the price according to the type, capacity and evaluation factor of the vehicle. The specific implementation is as follows:

const EVALUATION_FACTOR = Math.PI; 
function evaluatePrice(vehicle: Vehicle) {
  return vehicle.capacity * EVALUATION_FACTOR;
}

const myTruck: Truck = { vType: "truck", capacity: 9.5 };
evaluatePrice(myTruck);

For the above code, the TypeScript compiler will prompt the following error message:

Property 'capacity' does not exist on type 'Vehicle'.
Property 'capacity' does not exist on type 'Motorcycle'.

The reason is that in the Motorcycle interface, there is no capacity attribute, and for the Car interface, there is no capacity attribute. Now, how can we solve the above problems? At this time, we can use type guard. Let's refactor the evaluatePrice method defined earlier. The code after refactoring is as follows:

function evaluatePrice(vehicle: Vehicle) {
  switch(vehicle.vType) {
    case "car":
      return vehicle.transmission * EVALUATION_FACTOR;
    case "truck":
      return vehicle.capacity * EVALUATION_FACTOR;
    case "motorcycle":
      return vehicle.make * EVALUATION_FACTOR;
  }
}

In the above code, we use the switch and case operators to implement the type guard, so as to ensure that in the evaluatePrice method, we can safely access the attributes contained in the vehicle object to correctly calculate the price corresponding to the vehicle type.

5.3 type alias
Type aliases are used to give a type a new name.

type Message = string | string[];

let greet = (message: Message) => {
  // ...
};

6, Cross type
TypeScript cross type is to combine multiple types into one type. This allows us to stack the existing multiple types into one type, which contains all the required types of features.

interface IPerson {
  id: string;
  age: number;
}

interface IWorker {
  companyId: string;
}

type IStaff = IPerson & IWorker;

const staff: IStaff = {
  id: 'E1006',
  age: 33,
  companyId: 'EFT'
};

console.dir(staff)

In the above ex amp le, we first define different members for the type of IPerson and IWorker, and then define the IStaff cross type through the & operator, so this type has members of both types of IPerson and IWorker.

7, TypeScript functions

7.1 difference between typescript function and JavaScript function

7.2 arrow function
1. Common syntax
myBooks.forEach(() => console.log('reading'));

myBooks.forEach(title => console.log(title));

myBooks.forEach((title, idx, arr) =>
console.log(idx + '-' + title);
);

myBooks.forEach((title, idx, arr) => {
console.log(idx + '-' + title);
});

2. Use example

// Arrow function not used
function Book() {
  let self = this;
  self.publishDate = 2016;
  setInterval(function () {
    console.log(self.publishDate);
  }, 1000);
}

// Using arrow functions
function Book() {
  this.publishDate = 2016;
  setInterval(() => {
    console.log(this.publishDate);
  }, 1000);
}

7.3 parameter type and return type

function createUserId(name: string, id: number): string {
  return name + id;
}

7.4 function types

let IdGenerator: (chars: string, nums: number) => string;

function createUserId(name: string, id: number): string {
  return name + id;
}

IdGenerator = createUserId;

7.5 optional and default parameters

```cpp
// Optional parameters
function createUserId(name: string, id: number, age?: number): string {
  return name + id;
}

// Default parameters
function createUserId(
  name: string = "Semlinker",
  id: number,
  age?: number
): string {
  return name + id;
}
When you declare a function, you can define optional parameters with the? Sign, such as age: number. In actual use, it should be noted that optional parameters should be placed after normal parameters, otherwise compilation errors will be caused.

**7.6 remaining parameters**

```cpp
function push(array, ...items) {
  items.forEach(function (item) {
    array.push(item);
  });
}

let a = [];
push(a, 1, 2, 3);

7.7 function overload
Function overloading or method overloading is the ability to create multiple methods with the same name and different parameter numbers or types. To solve the problem, the method is to provide multiple function type definitions for the same function for function overloading, and the compiler will process function calls according to this list.

function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) {
  if (typeof a === "string" || typeof b === "string") {
    return a.toString() + b.toString();
  }
  return a + b;
}

In the above code, we provide multiple function type definitions for the add function to realize function overloading. After that, the nasty error message disappears again, because the result variable is of type string. In TypeScript, in addition to overloading ordinary functions, we can also overload member methods in classes.
Method overloading refers to a technology that a method in the same class has the same name and different parameters (different parameter types, different number of parameters or the sequence of parameters with the same number of parameters). When it is called, a method matching it is selected to perform operations according to the form of actual parameters. Therefore, the condition that the member methods in a class meet the overload is: in the same class, the method names are the same and the parameter lists are different. Let's take an example of a member method overload:

class Calculator {
  add(a: number, b: number): number;
  add(a: string, b: string): string;
  add(a: string, b: number): string;
  add(a: number, b: string): string;
  add(a: Combinable, b: Combinable) {
    if (typeof a === "string" || typeof b === "string") {
      return a.toString() + b.toString();
    }
    return a + b;
  }
}

const calculator = new Calculator();
const result = calculator.add("Semlinker", " Kakuqo");

Note here that when the TypeScript compiler handles function overloading, it looks up the overload list and tries to use the first overload definition. Use this if it matches. Therefore, when defining overload, we must put the most accurate definition at the top. In addition, in the Calculator class, add(a: Combinable, b: Combinable) {} is not part of the overload list, so for the add member method, we only define four overload methods.

8, TypeScript array

8.1 array deconstruction

let x: number; let y: number; let z: number;
let five_array = [0,1,2,3,4];
[x,y,z] = five_array;

8.2 array expansion operator

let two_array = [0, 1];
let five_array = [...two_array, 2, 3, 4];

8.3 array traversal

```cpp
let colors: string[] = ["red", "green", "blue"];
for (let i of colors) {
  console.log(i);
}

9, TypeScript object

9.1 object deconstruction

let person = {
  name: "Semlinker",
  gender: "Male",
};

let { name, gender } = person;

9.2 object expansion operator

let person = {
  name: "Semlinker",
  gender: "Male",
  address: "Xiamen",
};

// Assembly object
let personWithAge = { ...person, age: 33 };

// Get items other than some
let { name, ...rest } = person;

10, TypeScript interface
In object-oriented language, interface is a very important concept. It is an abstraction of behavior, and how to act needs to be implemented by class.
The interface in TypeScript is a very flexible concept. In addition to being used to abstract part of the behavior of a class, it is also used to describe the Shape of an object.
10.1 shape of objects

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

let Semlinker: Person = {
  name: "Semlinker",
  age: 33,
};

10.2 optional read only attribute

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

Read only properties are used to limit the value of an object to only be modified when it was just created. In addition, TypeScript also provides ReadonlyArray type, which is similar to Array, except that all variable methods are removed, so it can ensure that the Array cannot be modified after creation.

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!

11, TypeScript class

11.1 properties and methods of class
In the object-oriented language, class is the construction of an object-oriented computer programming language, the blueprint of creating objects, and describes the common properties and methods of the created objects.
In TypeScript, we can define a Class through the Class keyword:

class Greeter {
  // Static properties
  static cname: string = "Greeter";
  // Member properties
  greeting: string;

  // Constructor - perform initialization
  constructor(message: string) {
    this.greeting = message;
  }

  // Static method
  static getClassName() {
    return "Class name is Greeter";
  }

  // Member method
  greet() {
    return "Hello, " + this.greeting;
  }
}

let greeter = new Greeter("world");

What's the difference between member properties and static properties, and between member methods and static methods? Without too much explanation, let's take a look at the following ES5 code generated by compilation:

"use strict";
var Greeter = /** @class */ (function () {
    // Constructor - perform initialization
    function Greeter(message) {
        this.greeting = message;
    }
    // Static method
    Greeter.getClassName = function () {
        return "Class name is Greeter";
    };
    // Member method
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    // Static properties
    Greeter.cname = "Greeter";
    return Greeter;
}());
var greeter = new Greeter("world");

11.2 accessors
In TypeScript, we can use getter and setter methods to encapsulate data and verify validity to prevent abnormal data.

let passcode = "Hello TypeScript";

class Employee {
  private _fullName: string;

  get fullName(): string {
    return this._fullName;
  }

  set fullName(newName: string) {
    if (passcode && passcode == "Hello TypeScript") {
      this._fullName = newName;
    } else {
      console.log("Error: Unauthorized update of employee!");
    }
  }
}

let employee = new Employee();
employee.fullName = "Semlinker";
if (employee.fullName) {
  console.log(employee.fullName);
}

11.3 class inheritance
Inheritance is a hierarchical model that connects classes and classes. It refers to that a class (called subclass, sub interface) inherits the functions of another class (called parent class, parent interface) and can increase its own new functions. Inheritance is the most common relationship between a class and a class or between an interface and an interface.
Inheritance is an is-a relationship:

In TypeScript, we can implement inheritance through the extends keyword:

class Animal {
  name: string;
  
  constructor(theName: string) {
    this.name = theName;
  }
  
  move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

class Snake extends Animal {
  constructor(name: string) {
    super(name);
  }
  
  move(distanceInMeters = 5) {
    console.log("Slithering...");
    super.move(distanceInMeters);
  }
}

let sam = new Snake("Sammy the Python");
sam.move();

11.4 ECMAScript private fields
ECMAScript private fields have been supported since TypeScript 3.8. They are used as follows:

class Person {
  #name: string;

  constructor(name: string) {
    this.#name = name;
  }

  greet() {
    console.log(`Hello, my name is ${this.#name}!`);
  }
}

let semlinker = new Person("Semlinker");

semlinker.#name;
//     ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.

Unlike general properties (even those declared with the private modifier), private fields bear in mind the following rules:

  • The private field begins with the #, sometimes we call it the private name;
  • Each private field name is uniquely limited to the class it contains;
  • TypeScript accessibility modifiers (such as public or private) cannot be used on private fields;
  • Private fields cannot be accessed outside of the included classes or even detected.

12, TypeScript generics

In software engineering, we should not only create well-defined API, but also consider reusability. Components can not only support the current data types, but also the future data types, which provides you with very flexible functions when creating large systems.
In languages like C and Java, generics can be used to create reusable components, and a component can support multiple types of data. In this way, users can use components with their own data types.
The key purpose of designing generics is to provide meaningful constraints among members, which can be instance members of a class, methods of a class, function parameters, and function return values.
Generics is a template that allows the same function to accept different types of arguments. Using generics to create reusable components is better than using any types, because generics retain parameter types.
12.1 generic interface

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

12.2 generic classes

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;
};

12.3 generic variables
When you see the generic variables T and E, K and V, it's hard for you to see them. In fact, there is no essential difference between these capital letters, just a good standard. That is to say, type variables defined with capital letters A-Z belong to generics, and the same is true when t is replaced with a. Let's introduce the meaning of some common generic variables:

  • T (Type): indicates a TypeScript Type
  • K (Key): indicates the Key type in the object
  • V (Value): indicates the Value type in the object
  • E (Element): represents Element type

12.4 generic tool types

For the convenience of developers, TypeScript has built in some common tool types, such as Partial, Required, Readonly, Record and ReturnType. For the sake of space, we will only briefly introduce the Partial tool type here. However, before we make a specific introduction, we need to introduce some basic knowledge, so that readers can learn other tool types by themselves.
1.typeof
In TypeScript, the typeof operator can be used to get the type of a variable declaration or object.

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

const sem: Person = { name: 'semlinker', age: 30 };
type Sem= typeof sem; // -> Person

function toArray(x: number): Array<number> {
  return [x];
}

type Func = typeof toArray; // -> (x: number) => number[]

2.keyof
The keyof operator can be used for all key values in an object:

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

type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join" 
type K3 = keyof { [x: string]: Person };  // string | number

3.in

in Used to traverse enumeration types:
type Keys = "a" | "b" | "c"

type Obj =  {
  [p in Keys]: any
} // -> { a: any, b: any, c: any }

4.infer
In a conditional type statement, you can declare a type variable with infer and use it.

type ReturnType<T> = T extends (
  ...args: any[]
) => infer R ? R : any;

In the above code, infer R is to declare a variable to carry the return value type of the incoming function signature. In short, it is convenient to use it to get the return value type of the function.
5.extends
Sometimes the generics we define don't want to be too flexible or inherit some classes. You can add generic constraints through the extends keyword.

interface ILengthwise {
  length: number;
}

function loggingIdentity<T extends ILengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

Now that the generic function has constraints defined, it is no longer applicable to any type:

loggingIdentity(3);  // Error, number doesn't have a .length property

At this time, we need to pass in a value conforming to the constraint type, which must contain the required properties:

```cpp
loggingIdentity({length: 10, value: 3});

6.Partial
The function of Partial is to change all the attributes in a certain type to optional?.
definition:

/**
 * node_modules/typescript/lib/lib.es5.d.ts
 * Make all properties in T optional
 */
type Partial<T> = {
  [P in keyof T]?: T[P];
};

In the above code, first get all attribute names of T through keyof T, then use in to traverse, assign values to P, and finally get corresponding attribute values through T[P]. The? In the middle is used to make all attributes optional.
Example:

interface Todo {
  title: string;
  description: string;
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}

const todo1 = {
  title: "organize desk",
  description: "clear clutter",
};

const todo2 = updateTodo(todo1, {
  description: "throw out trash",
});

In the updateTodo method above, we use the Partial tool type to define the type of fieldsToUpdate as Partial, that is:

```cpp
{
   title?: string | undefined;
   description?: string | undefined;
}

13, TypeScript decorator

13.1 what is a decorator

  • It's an expression
  • When the expression is executed, it returns a function
  • The input parameters of the function are target, name and descriptor respectively
  • After the function is executed, the descriptor object may be returned to configure the target object

13.2 classification of decorators

  • Class decorators

  • Property decorators

  • Method decorators

  • Parameter decorators

Class 13.3 decorators
Class decorator declaration:

declare type ClassDecorator = <TFunction extends Function>(
  target: TFunction
) => TFunction | void;

As the name implies, the class decorator is used to decorate the class. It takes a parameter:

  • Target: tffunction - decorated class

After the first look, do you feel bad. It's OK. Let's take an example:

function Greeter(target: Function): void {
  target.prototype.greet = function (): void {
    console.log("Hello Semlinker!");
  };
}

@Greeter
class Greeting {
  constructor() {
    // Internal implementation
  }
}

let myGreeting = new Greeting();
myGreeting.greet(); // console output: 'Hello Semlinker!';

In the above example, we defined the Greeter class decorator, and we used @ Greeter syntax sugar to use the decorator.

Friendly tip: readers can directly copy the above code and run it in TypeScript Playground to view the results.

Some readers may want to ask: in the example, Hello Semlinker! Is always output. Can you customize the output greeting? This is a good question. The answer is yes.
The specific implementation is as follows:

function Greeter(greeting: string) {
  return function (target: Function) {
    target.prototype.greet = function (): void {
      console.log(greeting);
    };
  };
}

@Greeter("Hello TS!")
class Greeting {
  constructor() {
    // Internal implementation
  }
}

let myGreeting = new Greeting();
myGreeting.greet(); // console output: 'Hello TS!';

13.4 attribute decorator
Property decorator declaration:

declare type PropertyDecorator = (target:Object, 
  propertyKey: string | symbol ) => void;

As the name implies, the attribute decorator is used to decorate the attributes of a class. It takes two parameters:

target: Object - decorated class

propertyKey: string | symbol - property name of the decorated class

Strike while the iron is hot. Let's warm up for an example:

function logProperty(target: any, key: string) {
  delete target[key];

  const backingField = "_" + key;

  Object.defineProperty(target, backingField, {
    writable: true,
    enumerable: true,
    configurable: true
  });

  // property getter
  const getter = function (this: any) {
    const currVal = this[backingField];
    console.log(`Get: ${key} => ${currVal}`);
    return currVal;
  };

  // property setter
  const setter = function (this: any, newVal: any) {
    console.log(`Set: ${key} => ${newVal}`);
    this[backingField] = newVal;
  };

  // Create new property with getter and setter
  Object.defineProperty(target, key, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true
  });
}

class Person { 
  @logProperty
  public name: string;

  constructor(name : string) { 
    this.name = name;
  }
}

const p1 = new Person("semlinker");
p1.name = "kakuqo";

In the above code, we define a logProperty function to track the user's operation on the property. When the code runs successfully, the following results will be output on the console:

Set: name => semlinker
Set: name => kakuqo

13.5 method decorator
Method decorator declaration:

declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol,    
  descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void;

Method decorator, as the name implies, is used to decorate the methods of a class. It takes three parameters:

  • target: Object - decorated class

propertyKey: string | symbol - method name

  • Descriptor: typepropertydescription - property descriptor

Without much nonsense, let's take a direct example:

function LogOutput(tarage: Function, key: string, descriptor: any) {
  let originalMethod = descriptor.value;
  let newMethod = function(...args: any[]): any {
    let result: any = originalMethod.apply(this, args);
    if(!this.loggedOutput) {
      this.loggedOutput = new Array<any>();
    }
    this.loggedOutput.push({
      method: key,
      parameters: args,
      output: result,
      timestamp: new Date()
    });
    return result;
  };
  descriptor.value = newMethod;
}

class Calculator {
  @LogOutput
  double (num: number): number {
    return num * 2;
  }
}

let calc = new Calculator();
calc.double(11);
// console ouput: [{method: "double", output: 22, ...}]
console.log(calc.loggedOutput); 

Let's introduce the parameter decorator.
13.6 parameter decorator
Parameter decorator declaration:

declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, 
  parameterIndex: number ) => void

Parameter decorator, as the name implies, is used to decorate function parameters. It receives three parameters:

  • target: Object - decorated class
  • propertyKey: string | symbol - method name
  • parameterIndex: number - index value of the parameter in the method
function Log(target: Function, key: string, parameterIndex: number) {
  let functionLogged = key || target.prototype.constructor.name;
  console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has
 been decorated`);
}

class Greeter {
  greeting: string;
  constructor(@Log phrase: string) {
 this.greeting = phrase; 
  }
}

// console output: The parameter in position 0 
// at Greeter has been decorated

After introducing the basic knowledge related to the introduction of TypeScript, I guess many novice partners have the idea of "from getting started to giving up". Finally, let's briefly introduce the compilation context.

14, Compile context

fourteen point one tsconfig.json The role of

  1. The root path used to identify the TypeScript project;
  2. Used to configure TypeScript compiler;
  3. Specifies the compiled file.

fourteen point two tsconfig.json Important fields

  1. files - sets the name of the file to compile;

  2. include - set the files to be compiled to support path pattern matching;

  3. exclude - set files that do not need to be compiled, and support path pattern matching;

  4. compilerOptions - sets options related to the compilation process.

14.3 compiler options
Compilerpoptions supports many options, including baseUrl, target, baseUrl, moduleResolution and lib.
The details of each option are as follows:

{
  "compilerOptions": {

    /* Basic options */
    "target": "es5",                       // Specify ECMAScript target version: 'Es3' (default),'es5 ',' ES6 '/'es2015', 'es2016', 'es2017', or 'esnext'
    "module": "commonjs",                  // Specify the module to use: 'commonjs',' amd ',' system ',' UMD 'or' es2015 '
    "lib": [],                             // Specify the library files to include in the compilation
    "allowJs": true,                       // Allow javascript files to be compiled
    "checkJs": true,                       // Report errors in javascript files
    "jsx": "preserve",                     // Specify the generation of jsx Code: 'preserve', 'react native', or 'react'
    "declaration": true,                   // Generate corresponding '. d.ts' file
    "sourceMap": true,                     // Generate corresponding '. map' file
    "outFile": "./",                       // Merge output files into one file
    "outDir": "./",                        // Specify output directory
    "rootDir": "./",                       // Used to control the output directory structure -- outDir
    "removeComments": true,                // Delete all comments after compilation
    "noEmit": true,                        // Do not generate output file
    "importHelpers": true,                 // Import helper functions from tslib
    "isolatedModules": true,               // Make each file as a separate module (and ' ts.transpileModule 'similar)

    /* Strict type checking options */
    "strict": true,                        // Enable all strict type checking options
    "noImplicitAny": true,                 // Error reporting of any type implied in expressions and declarations
    "strictNullChecks": true,              // Enable strict null checking
    "noImplicitThis": true,                // An error is generated when the value of this expression is of type any
    "alwaysStrict": true,                  // Check each module in strict mode and add 'use strict' to each file

    /* Additional checks */
    "noUnusedLocals": true,                // Throw an error when there are unused variables
    "noUnusedParameters": true,            // Throw an error when there are unused parameters
    "noImplicitReturns": true,             // When not all code in a function has a return value, an error is thrown
    "noFallthroughCasesInSwitch": true,    // Reports a fallthrough error for the switch statement. (that is, the case statement of switch is not allowed to run through)

    /* Module resolution options */
    "moduleResolution": "node",            // Select module Resolution Policy: 'node'( Node.js ) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./",                       // Base directory for resolving non relative module names
    "paths": {},                           // List of module name to baseUrl based path mapping
    "rootDirs": [],                        // A list of root folders whose combined content represents the structural content of the project at run time
    "typeRoots": [],                       // List of files containing type declarations
    "types": [],                           // List of type declaration filenames to include
    "allowSyntheticDefaultImports": true,  // Allows default import from a module that does not have a default export set.

    /* Source Map Options */
    "sourceRoot": "./",                    // Specifies where the debugger should find the TypeScript file instead of the source file
    "mapRoot": "./",                       // Specifies where the debugger should find the map file instead of the build file
    "inlineSourceMap": true,               // Generate a single soucemaps file instead of generating different files from sourcemaps
    "inlineSources": true,                 // To generate code and sourcemaps into a file, the -- inlineSourceMap or -- sourceMap property must be set at the same time

    /* Other options */
    "experimentalDecorators": true,        // Enable decorator
    "emitDecoratorMetadata": true          // Provide metadata support for decorators
  }
}

See that the readers here are all "true love". If you still have more than enough, click collect and pay attention to me, and I will do my best to provide it to you

Tags: TypeScript Attribute Javascript calculator

Posted on Sun, 07 Jun 2020 03:13:07 -0700 by begeiste