Great introductory TypeScript tutorial (1.2W words)

Look at this for a little friend who wants to learn TypeScript. This article will take you step by step to learn 14 points related to getting started with TypeScript. See the following picture for a detailed outline:

1. What is TypeScript

TypeScript It 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 in 2015 and future proposals, such as asynchronous functionality and Decorators, to help build robust components.The following diagram shows the relationship between TypeScript and ES5, ES2015, and ES2016:

Differences between 1.1 TypeScript and JavaScript

TypeScript JavaScript
A superset of JavaScript addresses the code complexity of large projects A scripting language for creating dynamic Web pages.
Errors can be found and corrected during compilation As an interpretive language, errors can only be found at runtime
Strong type, supports static and dynamic types Weak type, no static type option
Eventually compiled into JavaScript code to make the browser understand Can be used directly in browsers
Supports modules, generics, and interfaces Modules, generics, or interfaces are not supported
Support for ES3, ES4, ES5, ES6, etc. Compiling other ES3, ES4, ES5, or ES6 features is not supported
Community support is still growing, but it's not that big A lot of community support and a lot of documentation and problem solving support

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 files

$ tsc helloworld.ts
# helloworld.ts => helloworld.js

Of course, for beginners of TypeScript, you can also use online instead of installing typescript TypeScript Playground To learn new grammar or features.

TypeScript Playground: https://www.typescriptlang.or...

2. TypeScript Basic Types

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 enumerations we can define some constants with names.Enumerations can be used to clearly express intentions or create a distinct set of use cases.TypeScript supports numeric and string-based enumeration.

1. Number 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 automatically grow from 1.Let me put it another way,Direction.SOUTH The value is 1,Direction.EAST The value is 2,Direction.WEST The value is 3.The above enumeration sample code is compiled to produce 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 version 2.4, we were 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 code for the above code is 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

Membership values for heterogeneous enumerations are a mixture of numbers and strings:

enum Enum {
  A,
  B,
  C = "C",
  D = "D",
  E = 8,
  F,
}

The ES5 code for the above code is as follows:

"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 = {}));

Looking at the generated ES5 code above, we can see that the number enumeration has more "reverse mappings" than the 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 in the type system (also known as a global supertype).

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

The any type is essentially an escape compartment for the type system.As a developer, this gives us a lot of freedom: TypeScript allows us to do anything with any type of value without having to do any prior checks.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 relaxed.With any type, it is easy to write code that is typed correctly but has problems at runtime.If we use any type, we will not be able to use the large number of protection mechanisms provided by TypeScript.TypeScript 3.0 introduced the unknown type to solve any problems.

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 (any) of the TypeScript type system.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 the value variable are considered to be of the correct type.But what happens when we try to assign values of type unknown to other types of variables?

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

The unknown type can only be assigned to any type and to the 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 kind of values are stored in the variable value.

Now let's see what happens when we try to do something with a value of type unknown.Here's what 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, none of these operations are considered to be of the correct type.By changing any type to unknowntype, we have changed the default setting that allows all changes to prohibit any changes.

2.8 Tuple Type

As we all know, arrays generally consist of the same type of values, but sometimes we need to store different types of values in a single variable, so we can use tuples.There are no tuples in JavaScript. Tuples are unique types in TypeScript and work like arrays.

Tuples can be used to define types with a limited number of unnamed attributes.Each property has an associated type.When using tuples, you must provide a value for each property.For a more intuitive understanding of the concept of tuples, let's look at a specific example:

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

In the code above, we defined a variable named tupleType whose type is an array of types [string, boolean], and then we initialized the tupleType variable in the correct order.Like arrays, we can access elements in tuples by subscripts:

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

When tuples are initialized, if a type mismatch occurs, such as:

tupleType = [true, "Semlinker"];

At this point, the TypeScript compiler will prompt the following error message:

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

This is obviously due to a type mismatch.When tuples are initialized, we must also provide values for each property, or errors will occur, such as:

tupleType = ["Semlinker"];

At this point, the TypeScript compiler will prompt 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 appears to be the opposite of any type, meaning that there is no type at all.When a function does not return a value, you usually see that its return value type is void:

// Declare that the return value of the function is 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 is important to note that declaring a variable of type void 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 --strictNullChecks tag, null and undefined can only be assigned to void s 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 an arrow function expression that always throws an exception or does not have a return value at all.

// A 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 take advantage of the features of the never type to achieve a comprehensive check, as shown in the following examples:

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

Notice in the else branch that we assign foo narrowed to never to a never variable that displays the declaration.If all the logic is correct, you should be able to compile and pass here.But if one day your colleague changed the type of Foo:

type Foo = string | number | boolean;

However, he forgot to modify the control flow in the controlFlowAnalysisWithNever method at the same time, when the foo type of the else branch is narrowed down to the boolean type, which makes it impossible to assign to the never type, a compilation error occurs.In this way, we can ensure that

The controlFlowAnalysisWithNever method always exhausted all possible types of Foo.From this example, we can draw the conclusion that using never to avoid adding new union types without corresponding implementations is to write code that is absolutely safe for types.

3. TypeScript Assertions

Sometimes you encounter situations where you know more about a value than TypeScript.Usually this occurs when you clearly know that an entity has a more precise type than its existing type.

By using type assertions, you can tell the compiler, "Trust me, I know what I'm doing."Type assertions are like type conversions in other languages, but do not undergo special data checks and constructs.It has no runtime impact but 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 guards

A type guard is some expression that performs a runtime check that guarantees the type in some scope. - The official TypeScript document

Type protection is an expression that performs runtime checks to ensure that the type is within a range.In other words, type protection guarantees that a string is a string, although its value can also be a numeric value.Type protection is not entirely different from feature detection; the main idea is to try to detect attributes, methods, or prototypes to determine how values are handled.There are currently four main ways to achieve type protection:

4.1 in keywords

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 typeof 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".However, TypeScript does not prevent you from comparing to other strings, and languages do not 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 padder narrows to'SpaceRepeatingPadder'
}

4.4 Custom type-protected type predicates

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

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

5. Joint Types and Type Aliases

5.1 Union Type

The union type is usually used with null or undefined:

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

For example, the type of name here is string | undefined, which means you can pass a string or undefined value to the sayHello function.

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

From this example, you can intuitively know that the type combined by type A and type B is the type that accepts both A and B values.

5.2 Recognizable Joint

TypeScript recognizes the Discriminated Unions type, also known as the algebraic data type or label union type.It contains three main points: identifiability, joint type, and type guard.

This type is essentially a type protection method that combines a union type with a literal quantity type.If a type is a union of multiple types and multiple types contain a common attribute, you can use that common attribute to create different types of protected areas.

1. Identifiable

Recognition requires that each element in a union type contain a single 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 Motorcycle, Car, and Truck interfaces, each of which contains a vType attribute, which is called a recognizable attribute, while other attributes are only related to the interface of the attribute.

2. Joint Type

Based on the three interfaces defined above, we can create a Vehicle union type:

type Vehicle = Motorcycle | Car | Truck;

Now we can start using the Vehicle union type, which can represent different types of vehicles for Vehicle-type variables.

3. Type guard

Let's define an evaluatePrice method that calculates prices based on the type, capacity, and evaluation factor of a vehicle 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 there is no capacity attribute in the Motorcycle interface, and for the Car interface, there is no capacity attribute.So, how should we solve these problems now?At this point, we can use type guards.Let's refactor the evaluatePrice method that was defined earlier. The refactored code 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 code above, we use the switch and case operators to implement type guarding, which ensures that in the evaluatePrice method, we can safely access the properties contained in the vehicle object to correctly calculate the price for that 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 Types

TypeScript crossover types combine multiple types into one type.This allows us to overlay existing types into one type, which contains all the required characteristics.

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 ex amp le above, we first defined different members for the IPerson and IWorker types, then defined the IStaff cross type through the &operator, so this type has both IPerson and IWorker members.

7. TypeScript functions

7.1 Differences between TypeScript and JavaScript functions

TypeScript JavaScript
Contains Type No Type
Arrow function Arrow Function (ES2015)
Function type No function type
Required and optional parameters All parameters are optional
Default parameters Default parameters
Remaining parameters Remaining parameters
function overloading No Function Overload

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 examples

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

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

7.3 Parameter Types and Return Types

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

7.4 Function Type

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

// 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 declaring a function, optional parameters, such as age?: number, can be defined by the? Sign.In practice, it is important to note that the optional parameters should be placed after the common parameters, or they will result in compilation errors.

7.6 Remaining parameters

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

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

7.7 Function overload

Function overload or method overload is the ability to create multiple methods with the same name and different number or types of parameters.To solve the previous problem, the compiler handles function calls based on this list by providing multiple function type definitions for the same function to overload the function.

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 code above, we have provided several function type definitions for the add function to overload the function.Then the hateful error message disappears because the result variable is of type string.In addition to overloading normal functions in TypeScript, we can also overload member methods in classes.

Method overload refers to a technique that chooses a method matching it to perform an operation in the same class with the same name and different parameters (different parameter types, different number of parameters, or different order of parameters when the number of parameters is the same).So a member method in a class satisfies an overload if the method names are the same and the parameter lists are different in the same class.Let's take an example of 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");

It is important to note here that when the TypeScript compiler handles function overloads, it looks up the list of overloads and attempts to use the first overload definition.Use this if it matches.Therefore, when defining overloads, be sure to put the most accurate definition first.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

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

9. TypeScript Objects

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 languages, interface is an important concept, it is an abstraction of behavior, and how to act needs to be implemented by classes.

Interfaces in TypeScript are a very flexible concept, except when used for Abstracting part of a class's behavior In addition, it is often used to describe the Shape of an object.

Shape of 10.1 Object

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

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

10.2 Optional | Read-only Properties

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

Read-only properties are used to restrict the value of an object from being modified only when it was just created.In addition, TypeScript provides a ReadonlyArray <T>type, which is similar to Array <T>except that all mutable methods are removed, so you can ensure that the array can no longer be modified after it is created.

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 Classes

Attributes and methods of class 11.1

In object-oriented languages, classes are the construction of an object-oriented computer programming language, a blueprint for creating objects, and describe the common properties and methods of the objects created.

In TypeScript, we can define a class by using 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");

So what is the difference between member properties and static properties, and between member methods and static methods?Without much explanation, let's look directly at the ES5 code generated by the following 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 and verify the validity of data 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);
}

Inheritance of class 11.3

Inheritance is a hierarchical model that links classes to classes.Refers to a class (called a subclass, a subinterface) that inherits the functionality of another class (called a parent class, a parent interface) and can add its own new functionality. Inheritance is the most common relationship between a class and a class or interface and an interface.

Inheritance is a is-a Relationships:

In TypeScript, we can inherit by using 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 version 3.8 in the following ways:

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 regular attributes (even those declared with privatemodifiers), private fields have the following rules to keep in mind:

  • Private fields begin with the #character, sometimes called private names;
  • 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 the included classes or even detected.

Twelve. TypeScript Generics

In software engineering, we should not only create well-defined API s that are consistent, but also consider reusability.Components not only support current data types, but also future data types, which provide you with very flexible functionality when creating large systems.

In languages like C# and Java, you can use generics to create reusable components that can support multiple types of data.This allows users to use components with their own data types.

The key purpose of designing generics is to provide meaningful constraints between 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 parameters.Using generics to create reusable components is better than using any types because they 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

For small partners who are new to TypeScript generics, it is expected that they will look confused when they see generic variables T and E, as well as K and V.In fact, there is no essential difference between these capital letters, it is just a well-agreed specification.That is, type variables defined with the capital letter A-Z are generic, and the same is true when T is replaced with A.Here's what some common generic variables mean:

  • T (Type): Represents a TypeScript type
  • K(Key): Represents the key type in an object
  • V (Value): Represents the type of value in the object
  • E (Element): Represents the element type

12.4 Generic Tool Types

For developer convenience, TypeScript has built-in common tool types such as Partial, Required, Readonly, Record, and ReturnType.For space reasons, here we will only briefly describe the Partial tool type.Before we do that, however, we need to introduce some basic knowledge to make it easier for readers to learn about other types of tools on their own.

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 is 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 code above, infer R declares a variable to hold the return value type of the incoming function signature, which is simply the type to which the return value of the function is retrieved for convenience.

5.extends

Sometimes the generics we define don't want to be too flexible or inherit certain classes, etc. 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 is defined with constraints, it no longer applies to any type:

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

At this point we need to pass in a value that matches the constraint type and must contain the necessary attributes:

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

6.Partial

The purpose of Partial<T>is to make all the attributes in a type 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 code above, all the property names of T are obtained by keyof T first, then traversed by in, assigning the value to P, and finally the corresponding property values are obtained by T[P].The? Sign in the middle, 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, using the Partial<T>tool type, we define the type of fieldsToUpdate as Partial<Todo>, that is:

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

13. TypeScript Decorators

13.1 What is an ornament

  • It is an expression
  • When the expression is executed, a function is returned
  • The functions are target, name, and descriptor, respectively.
  • After executing the function, a descriptor object may be returned to configure the target object

13.2 Classification of ornaments

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

Class decorators, as the name implies, are used to decorate classes.It takes a parameter:

  • target: TFunction - Decorated class

After the first look, it doesn't feel good.Okay, 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 example above, we define a Greeter class decorator, and we use @Greeter grammatical sugar to use the decorator.

Friendship Tip: Readers can copy the code directly above, at TypeScript Playground Run in to view the results.

Some readers may want to ask, the example always outputs Hello Semlinker!, can you customize the output greeting?That's 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

Attribute decorator declaration:

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

Attribute decorators, as the name implies, are used to decorate attributes of classes.It takes two parameters:

  • target: Object - Decorated class
  • propertyKey: string | symbol - Attribute name of decorated class

To warm up while the iron is hot, let's take 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 code above, we define a logProperty function to track user actions on attributes, and when the code runs successfully, the following results are output in 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 decorators, as the name implies, are used to decorate methods of classes.It takes three parameters:

  • target: Object - Decorated class
  • propertyKey: string | symbol - method name
  • descriptor: TypePropertyDescript - Property Descriptor

Say nothing more, just to say:

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 talk about the parameter decorator.

13.6 Parameter Decorator

Parameter decorator declaration:

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

The parameter decorator, as its name implies, is used to decorate function parameters, which receive 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 basics of getting started with TypeScript, guess that many beginners already have the idea of "getting started to give up". Finally, let's briefly introduce the compilation context.

14. Compilation Context

14.1Tsconfig.jsonRole

  • Used to identify the root path of a TypeScript project;
  • Used to configure the TypeScript compiler;
  • Used to specify compiled files.

14.2Tsconfig.jsonImportant fields

  • files - Sets the name of the file to be compiled;
  • include - Sets the files that need to be compiled to support path pattern matching;
  • exclude - Sets files that do not need to be compiled to support path pattern matching;
  • compilerOptions - Sets options related to the compilation process.

14.3 CompoilerOptions option

CompoilerOptions supports many options, including baseUrl, target, baseUrl, moduleResolution, lib, and so on.

Each option of compilerOptions is described in detail as follows:

{
  "compilerOptions": {

    /* Basic Options */
    "target": "es5",                       // Specify the target version of ECMAScript:'ES3'(default),'ES5','ES6'/'ES2015','ES2016','ES2017', or'ESNEXT'
    "module": "commonjs",                  // Specify usage modules:'commonjs','amd','system','umd'or'es2015'
    "lib": [],                             // Specify the library files to be included in the compilation
    "allowJs": true,                       // Allow compilation of javascript files
    "checkJs": true,                       // Report errors in javascript files
    "jsx": "preserve",                     // Specify 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 compiled comments
    "noEmit": true,                        // Do not generate output file
    "importHelpers": true,                 // Importing auxiliary tool functions from tslib
    "isolatedModules": true,               // Make each file a separate module (with 'ts.transpileModule'similar'.

    /* Strict type checking options */
    "strict": true,                        // Enable all strict type checking options
    "noImplicitAny": true,                 // Error when implicit any type on expression and declaration
    "strictNullChecks": true,              // Enable strict null checking
    "noImplicitThis": true,                // An error is generated when the this expression value 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,             // Throw an error when not all the code in the function has a return value
    "noFallthroughCasesInSwitch": true,    // Report fallthrough error of switch statement.(that is, switch case statements are not allowed to run through)

    /* Module resolution options */
    "moduleResolution": "node",            // Select the 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 names to baseUrl-based path mappings
    "rootDirs": [],                        // A list of root folders whose combined content represents the structural content of the project runtime
    "typeRoots": [],                       // List of files containing type declarations
    "types": [],                           // List of type declaration filenames to include
    "allowSyntheticDefaultImports": true,  // Allows default imports from modules that do 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": "./",                       // Specify where the debugger should find the mapping file instead of generating it
    "inlineSourceMap": true,               // Generate a single soucemaps file instead of a different one
    "inlineSources": true,                 // Generating code into a file with sourcemaps requires that the--inlineSourceMap or--sourceMap property be set at the same time

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

Readers here are all "true love". If you still don't want to, take a look at the 1.5K+ open source project I organized on Github: awesome-typescript.

https://github.com/semlinker/...

15. Reference Resources

Created a "Re-learn TypeScript" group of micro-mails, want to group small partners, plus I "semlinker" micro-mails, with a focus on learning TS.Forty TS series tutorials have been published.

Keywords: Javascript TypeScript Attribute calculator

Added by pennythetuff on Tue, 09 Jun 2020 04:19:48 +0300