For those of you who want to learn TypeScript, this article will guide you through 14 basic TypeScript basics. See below for a detailed outline:

If you want to learn TypeScript generics systematically, read this article to understand TypeScript generics and applications (7.8k).

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 typing and class-based object-oriented programming to the language.

TypeScript offers the latest and evolving JavaScript features, including those from ECMAScript 2015 and future proposals, such as asynchronous functionality and Decorators, to help build robust components. The following figure shows the relationship between TypeScript and ES5, ES2015, and ES2016:

1.1 Differences between TypeScript and JavaScript

TypeScript JavaScript
Supersets of JavaScript are used to address the code complexity of large projects A scripting language used to create dynamic web pages.
Errors can be found and corrected during compilation As an interpreted language, errors can only be found at runtime
Strong typing, support for static and dynamic typing Weakly typed, with no static typing option
It is eventually compiled into JavaScript code that the browser can understand It can be used directly in the browser
Supports modules, generics, and interfaces Modules, generics, or interfaces are not supported
Supports ES3, ES4, ES5, and ES6 Other ES3, ES4, ES5, or ES6 functions are not supported
Community support is still growing, and it’s not huge yet Lots of community support and lots of documentation and problem-solving support

1.2 get TypeScript

The command-line TypeScript compiler can be installed using the node.js package.

1. Install the TypeScript

$ npm install -g typescript
Copy the code

2. Compile TypeScript files

$ tsc helloworld.ts
# helloworld.ts => helloworld.js
Copy the code

Of course, people who are new to TypeScript can use the online TypeScript Playground to learn new syntax or features without installing TypeScript.

TypeScript Playground:www.typescriptlang.org/play/

Base TypeScript types

2.1 a Boolean type

let isDone: boolean = false;
// ES5: var isDone = false;
Copy the code

2.2 Number type

let count: number = 10;
// ES5: var count = 10;
Copy the code

2.3 type String

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

2.4 Array type

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

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

2.5 Enum type

Using enumerations we can define constants with names. Use enumerations to articulate intent or to create a distinct set of use cases. TypeScript supports numeric and string-based enumerations.

1. Enumeration of numbers

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

let dir: Direction = Direction.NORTH;
Copy the code

By default, NORTH starts at 0, and the remaining members automatically grow from 1. In other words, the value of direction.south is 1, the value of direction.east is 2, and the value of direction.west is 3. The enumeration sample code above compiles 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;
Copy the code

We can also set the initial value of NORTH, such as:

enum Direction {
  NORTH = 3,
  SOUTH,
  EAST,
  WEST,
}
Copy the code

2. String enumeration

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

enum Direction {
  NORTH = "NORTH",
  SOUTH = "SOUTH",
  EAST = "EAST",
  WEST = "WEST",}Copy the code

The above code for ES5 is as follows:

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

3. Heterogeneous enumeration

The member values of a heterogeneous enumeration are a mixture of numbers and strings:

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

The above code for ES5 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 = {}));
Copy the code

By looking at the generated ES5 code above, we can see that numeric enumerations are more “reverse mapped” than string enumerations:

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

2.6 Any type

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

let notSure: any = Awesome!;
notSure = "Semlinker";
notSure = false;
Copy the code

The any type is essentially an escape pod for the type system. As developers, this gives us a lot of freedom: TypeScript allows us to do anything with a value of type any without performing any kind of checks beforehand. Such as:

let value: any;

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

In many scenarios, this is too loose. Using the any type, you can easily write code that is of the right type but problematic at run time. If we use any, we won’t be able to use many of TypeScript’s protections. To address the problem of any, TypeScript 3.0 introduces unknown types.

2.7 Unknown type

Just as all types can be assigned to any, all types can be assigned to unknown. This makes Unknown another top-level type in the TypeScript type system (the other is any). Here’s an example of how unknown can be used:

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(a);// OK
value = Symbol("type"); // OK
Copy the code

All assignments to the value variable are considered type-correct. 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
Copy the code

The unknown type can only be assigned to the any type and the unknown type itself. Intuitively, this makes sense: Only containers that can hold values of any type can hold values of type unknown. 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 operate on a value of type unknown. Here is the same action we looked at in the previous Any chapter:

let value: unknown;

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

With the value variable type set to unknown, these operations are no longer considered type-correct. By changing the any type to the unknown type, we have changed the default setting, which allows all changes, to disallow any changes.

2.8 the Tuple type

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

Tuples can be used to define types that have a limited number of unnamed attributes. Each attribute has an associated type. When using tuples, you must supply the value of each attribute. To make the concept of tuples more intuitive, let’s look at a concrete example:

let tupleType: [string.boolean];
tupleType = ["Semlinker".true];
Copy the code

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 variables in order of the correct type. As with arrays, we can access the elements of a tuple by subscript:

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

If a type mismatch occurs during tuple initialization, for example:

tupleType = [true."Semlinker"];
Copy the code

At this point, the TypeScript compiler displays the following error message:

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

It’s obviously a type mismatch. During tuple initialization, we must also provide the value of each attribute, otherwise we will also have an error, such as:

tupleType = ["Semlinker"];
Copy the code

At this point, the TypeScript compiler displays the following error message:

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

2.9 Void type

In some ways, the void type looks like the opposite of any. It means there is no type. When a function does not return a value, you will usually see a return value of type void:

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

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

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

Note that declaring a void variable has no effect, since its value can only be undefined or null:

let unusable: void = undefined;
Copy the code

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;
Copy the code

By default null and undefined are subtypes of all types. That means you can assign null and undefined to a variable of type number. However, if you specify the –strictNullChecks tag, 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 will never exist. For example, the type never is the return value type for function or arrow function expressions that always throw an exception or never return a value at all.

// A function that returns never must have an unreachable endpoint
function error(message: string) :never {
  throw new Error(message);
}

function infiniteLoop() :never {
  while (true) {}}Copy the code

In TypeScript, you can use the never feature to implement comprehensiveness checks. Here’s an example:

type Foo = string | number;

function controlFlowAnalysisWithNever(foo: Foo) {
  if (typeof foo === "string") {
    // Here foo is narrowed to string
  } else if (typeof foo === "number") {
    // Here foo is narrowed to the number type
  } else {
    // Foo in this case is never
    constcheck: never = foo; }}Copy the code

Notice that in the else branch, we assign foo, narrowed down to never, to a variable that displays the declaration of never. If all logic is correct, then this should compile through. But suppose one day your colleague changes the type of Foo:

type Foo = string | number | boolean;
Copy the code

But he forgot to modify controlFlowAnalysisWithNever method in control process at the same time, this time the else branch foo type can be narrowed to a Boolean type, lead to cannot be assigned to never type, then will generate a compiler error. In this way, we can make sure that

ControlFlowAnalysisWithNever method always end with all possible types of Foo. From this example, we can conclude that using never avoids the possibility that there is no corresponding implementation for a newly joined type, in order to write type-safe code.

TypeScript assertions

Sometimes you will encounter situations where you know more about a value than TypeScript does. Usually this happens when you clearly know that an entity has a more precise type than it already has.

Type assertions are a way of telling the compiler, “Trust me, I know what I’m doing.” Type assertions are like conversions in other languages, but without the special data checking and deconstruction. It has no runtime impact, only at compile time.

Type assertions come in two forms:

3.1 Angle bracket syntax

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

3.2 the as grammar

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

Type guard

A type guard is some expression that pers A runtime check that guarantees the type in some scope

A type guard is an expression that can be checked at runtime to ensure that the type is within a certain range. In other words, type protection guarantees that a string is a string, even though 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 stereotypes to determine how to handle values. At present, there are four main ways to realize 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); }}Copy the code

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}'. `);
}
Copy the code

Only two types of typeof protection are supported: Typeof V === “typename” and Typeof V! == TYPename, “typename” must be “number”, “string”, “Boolean” or “symbol”. But TypeScript doesn’t prevent you from comparing other strings; the language doesn’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 padder type is narrowed to 'SpaceRepeatingPadder'
}
Copy the code

4.4 User-defined type predicates for type protection

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

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

Join types and type aliases

5.1 Union Type

Union types are usually used with null or undefined:

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

Here, for example, the type of the name is the string | undefined means that can be a string or undefined values passed to the sayHello function.

sayHello("Semlinker");
sayHello(undefined);
Copy the code

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

5.2 Discernable association

TypeScript Discriminated Unions types are also known as algebraic data types or label union types. It has three main points: identifiers, federated types, and type guards.

The essence of this type is a type protection method that combines union types and literal types. If a type is a union type of multiple types, and the types have a common property, you can use this common property to create different types of protected blocks.

1. Can recognize

Identifiers require 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
}
Copy the code

In the above code, we define three interfaces, namely Motorcycle, Car and Truck, and each of these interfaces contains a vType attribute, which is called the identifiable attribute, while other attributes are only related to the characteristic interface.

2. Union type

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

type Vehicle = Motorcycle | Car | Truck;
Copy the code

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

3. Type guard

Let’s define an evaluatePrice method to calculate the price based on the vehicle type, capacity, and evaluation factor, 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);
Copy the code

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

Property 'capacity' does not exist on type 'Vehicle'.
Property 'capacity' does not exist on type 'Motorcycle'.
Copy the code

The reason is that there is no capacity attribute for Motorcycle, and there is no capacity attribute for Car. So how can we solve these problems now? At this point, we can use the type guard. Let’s refactor the evaluatePrice method to look like this:

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

In the above code, we implement the type guard using the switch and case operators to ensure that in the evaluatePrice method, we can safely access the properties contained in the Vehicle object to correctly calculate the price for the vehicle type.

5.3 Type Alias

A type alias is used to give a type a new name.

type Message = string | string[];

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

Cross type

TypeScript cross-typing combines multiple types into a single type. This allows us to add existing types together into a single type that contains all the required features of the type.

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)
Copy the code

In the above example, we first defined different members for the IPerson and IWorker types, and then we defined the IStaff crossover type with the & operator, so that the type has members of both IPerson and IWorker types.

TypeScript functions

7.1 Differences between TypeScript functions and JavaScript functions

TypeScript JavaScript
Contains the type No type
Arrow function Arrow function (ES2015)
Function types No function type
This parameter is mandatory and optional All parameters are optional
The default parameters The default parameters
The remaining parameters The remaining parameters
Function overloading No function overload

7.2 Arrow Function

1. Common grammar

myBooks.forEach((a)= > console.log('reading'));

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

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

2. Example

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

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

7.3 Parameter Types and Return Types

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

7.4 Function Types

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

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

IdGenerator = createUserId;
Copy the code

7.5 This parameter is optional

Function createUserId(name: string, ID: number, age? : number): string { return name + id; } function createUserId(name: string = "Semlinker", id: number, age? : number ): string { return name + id; }Copy the code

When you declare a function, you can pass? Number to define optional parameters, such as age? : number In practice, it is important to place the optional arguments after the normal arguments, otherwise it will cause a compilation error.

7.6 Remaining Parameters

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

let a = [];
push(a, 1.2.3);
Copy the code

7.7 Function Overloading

Function overload or method overload is the ability to create multiple methods with the same name and different numbers or types of arguments. The way to solve the previous problem is to overload the same function by providing multiple function type definitions, and the compiler will handle the call based on 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;
}
Copy the code

In the code above, we overload the add function by providing multiple function type definitions. After that, the nasty error message disappears again, because the result variable is of type String. In TypeScript, in addition to overloading normal functions, we can also override member methods in classes.

Method overloading is a technique in which a method with the same name in the same class has different parameters (different parameter types, different number of parameters, or different order of parameters for the same number of parameters), and the method that matches the argument is selected to perform the operation based on the form of the argument. So a member method in a class can be overloaded only if it has the same name and different argument lists in the same class. Here’s an example of member method overloading:

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();
    }
    returna + b; }}const calculator = new Calculator();
const result = calculator.add("Semlinker"." Kakuqo");
Copy the code

The important thing to note here is that when the TypeScript compiler handles function overloading, it looks up the list of overloads and tries to use the first overload definition. Use this if there is a match. Therefore, when defining overloads, be sure to put the most precise definition first. Also in the Calculator class, add(A: Combinable, B: Combinable){} is not part of the overload list, so we only define four overload methods for the Add member method.

TypeScript arrays

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;
Copy the code

8.2 Array expansion operator

let two_array = [0.1];
let five_array = [...two_array, 2.3.4];
Copy the code

8.3 Array Traversal

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

9. TypeScript objects

9.1 Object Deconstruction

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

9.2 Object expansion operator

let person = {
  name: "Semlinker",
  gender: "Male",
  address: "Xiamen"};// Assemble the object
letpersonWithAge = { ... person, age:33 };

// Get all but certain items
let{ name, ... rest } = person;Copy the code

TypeScript interfaces

In object-oriented language, interface is a very important concept, it is the abstraction of behavior, and the concrete action needs to be implemented by the class.

Interfaces in TypeScript are a very flexible concept. In addition to abstracting part of the behavior of a class, they are often used to describe the Shape of an object.

10.1 Shapes of objects

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

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

10.2 optional | read-only property

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

The read-only property is used to restrict the value of an object to be changed only when it is newly created. TypeScript also provides ReadonlyArray

, which is similar to Array

except that all mutable methods are removed, thus ensuring that the Array cannot be modified once it has been 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!
Copy the code

TypeScript classes

11.1 Properties and methods of the class

In an object-oriented language, a class is a construct of an object-oriented computer programming language. It is a blueprint for creating an object, describing the common properties and methods of the created object.

In TypeScript, we can define a Class using the Class keyword:

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

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

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

  // Member methods
  greet() {
    return "Hello, " + this.greeting; }}let greeter = new Greeter("world");
Copy the code

So what’s the difference between a member property and a static property, or between a member method and a static method? Without further explanation, let’s take a look at the compiled ES5 code:

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

11.2 the visitor

In TypeScript, getters and setters are used to encapsulate and validate 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);
}
Copy the code

11.3 Class inheritance

Inheritance is a hierarchical model that connects classes to classes. The ability for a class (called a subclass, or subinterface) to inherit the functionality of another class (called a parent class, or parent interface) and add new functionality of its own. Inheritance is the most common relationship between classes or interfaces.

Inheritance is an IS-A relationship:

In TypeScript, we can implement inheritance with 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();
Copy the code

11.4 ECMAScript Private Fields

ECMAScript private fields have been supported since TypeScript version 3.8. They can be 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.
Copy the code

Unlike regular properties (even those declared with the private modifier), keep the following rules in mind for private fields:

  • Private fields to#Character, sometimes we call it a private name;
  • Each private field name is uniquely qualified to the class it contains;
  • You cannot use TypeScript accessibility modifiers (such as public or private) on private fields;
  • Private fields cannot be accessed outside of the contained class and cannot even be detected.

TypeScript generics

In software engineering, we should not only create consistent, well-defined apis, but also consider reusability. Components can support not only current data types, but future data types as well, which gives you great flexibility 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: instance members of the class, methods of the class, function arguments, and function return values.

Generics is a template that allows the same function to accept parameters of different types. Instead of using the any type, it is better to use generics to create reusable components because generics preserve the parameter types.

12.1 Generic Interface

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

12.2 a generic class

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;
};
Copy the code

12.3 Generic variables

The generic variables T and E, K and V can be a bit of a shock to anyone new to TypeScript generics. In fact, there is no essential difference between these capital letters, it is just a agreed upon specification. That is, if you define A type variable with capital letters A to Z, it is A generic type. If you change T to A, it is the same. Here’s what some common generic variables mean:

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

12.4 Generic tool types

Common utility types such as Partial, Required, Readonly, Record, and ReturnType are built into TypeScript for developers’ convenience. For the sake of space, we’ll only briefly cover the Partial tool types here. But before we get into that, let’s cover some basics so you can learn about the other types of tools on your own.

1.typeof

In TypeScript, the typeof operator can be used to get the typeof 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[]
Copy the code

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
Copy the code

3.in

In is used to iterate over enumeration types:

type Keys = "a" | "b" | "c"

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

4.infer

In conditional type statements, you can use infer to declare a type variable and use it.

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

In the preceding code, infer R means to declare a variable to carry the type of the return value passed into the function signature. In other words, infer can be used to obtain the type of the return value of the function for later use.

5.extends

Sometimes when we define a generic that we don’t want to be too flexible or want to inherit from some class, we can add a generic constraint with the extends keyword.

interface ILengthwise {
  length: number;
}

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

Now the generic function is defined with constraints, so it no longer applies to any type:

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

In this case, we need to pass in values that conform to the constraint type, which must contain the required attributes:

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

6.Partial

Partial

makes all properties of a certain type optional, right? .

Definition:

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

In the above code, we first get all the attribute names of T through keyof T, then use in to iterate, assign the value to P, and finally get the corresponding attribute value through T[P]. In the middle? The number used to make all properties 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"});Copy the code

In the updateTodo method above, we use the Partial

tool type and define the fieldsToUpdate to be of type Partial

, that is:

{ title? :string | undefined; description? :string | undefined;
}
Copy the code

TypeScript decorators

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
  • This function may return an object that is used to configure the Target object

13.2 Classification of decorators

  • Class decorators
  • Property decorators
  • Method decorators
  • Parameter decorators

Class 13.3 decorators

Class decorators declare:

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

Class decorators, as the name suggests, decorate classes. It takes a parameter:

  • Target: TFunction – The class to be decorated

After the first look, you don’t even feel good. Ok, let’s do 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! ';
Copy the code

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

As a bonus, you can simply copy the above code and run it in your TypeScript Playground to see the results.

As some readers may be wondering, the example always prints Hello Semlinker! , can you customize the output greeting? That’s a good question, and 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! ';
Copy the code

13.4 Property decorators

Property decorator declaration:

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

Property decorators, as the name suggests, decorate the properties of a class. It takes two arguments:

  • Target: Object – The class to be decorated
  • PropertyKey: string | symbol – be decorating class attribute names

Strike while the iron is hot. Here’s an example to warm up:

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";
Copy the code

We define a logProperty function to track the user’s actions on the property. When the code runs successfully, the console will print the following:

Set: name => semlinker
Set: name => kakuqo
Copy the code

13.5 Method decorators

Method decorator declaration:

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

Method decorators, as the name suggests, decorate the methods of a class. It takes three arguments:

  • Target: Object – The class to be decorated
  • PropertyKey: string | symbol – the method name
  • Descriptor: TypePropertyDescript – Property descriptor

Without further ado, let’s go to the following 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); 
Copy the code

Let’s introduce the parameter decorator.

13.6 Parameter decorators

Parameter decorator declaration:

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

The parameter decorator, as its name suggests, decorates function arguments. It takes three arguments:

  • Target: Object – The class to be decorated
  • PropertyKey: string | symbol – the method name
  • ParameterIndex: number – Specifies the index value of a parameter in a 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
Copy the code

After covering the basics of getting started with TypeScript, and guessing that many people who are just getting started with TypeScript already have the idea of “getting started and giving up”, let’s briefly introduce the compilation context.

Xiv. Compilation context

14.1 Functions of tsconfig.json

  • The root path that identifies a TypeScript project;
  • Configure the TypeScript compiler;
  • Used to specify the file to compile.

14.2 tsconfig.json Important fields

  • Files – Sets the name of the file to compile;
  • Include – sets the files to be compiled and supports path pattern matching.
  • Exclude – Sets the files that do not need to be compiled to support path pattern matching.
  • CompilerOptions – Sets the options related to the compile process.

14.3 compilerOptions options

CompilerOptions supports many options, such as baseUrl, Target, baseUrl, moduleResolution, and lib.

The details for each option are as follows:

{"compilerOptions": {/* compilerOptions */ "target": "es5", // specify the 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 "allowJs" to be included in the compilation: "CheckJs ": true, // Report errors in javascript files" JSX ": "preserve", // specify JSX code generation: 'preserve', 'react native', or 'react' 'declaration' : true, '.d.ts' = 'sourceMap' : True, / / generate the corresponding 'map' file "outFile" : ". / ", / / the output files into one file "outDir" : ". / ", / / specifies the output directory "rootDir" : "RemoveComments ": true, // remove all compiled comments "noEmit": true, // do not generate the output file "importHelpers": True, // Import the helper function isolatedModules from tslib: True, // treat each file as a separate module (similar to 'ts.transpilemodule'). /* Strict type checking options */ "strict": true, // enable all strict type checking options "noImplicitAny": True, // error "strictNullChecks": true, // enable strictNullChecks" noImplicitThis": True, // Generates an error "alwaysStrict" when this expression is of type any: True, // check each module in strict mode, and add 'use strict' to each file /* additional check */ "noUnusedLocals": true, // throw the error "noUnusedParameters" if there are unused variables: True, / / a unused parameters, throw an error "noImplicitReturns" : true, / / not all there is in the function code when the return value, throw an error "noFallthroughCasesInSwitch" : True, // Reports a fallthrough error for the switch statement. /* moduleResolution options */ "moduleResolution": 'node' (node.js) or 'classic' (TypeScript pre-1.6) "baseUrl": "./", // for resolving non-relative module names for the base "paths": {}, // list of module names to baseurL-based pathmapping "rootDirs": [], // list of root folders whose combined contents represent the project runtime structure content "typeRoots": [], // list of files containing type declarations "types": [], / / need to include the type of statement file name list "allowSyntheticDefaultImports" : true, / / allows never to set the default import export modules by default. /* 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 mapping file instead of generating the file "inlineSourceMap": true, // generate a single soucemaps file instead of sourcemaps generating different files "inlineSources": "ExperimentalDecorators ": // generate code and sourcemaps in a file that requires both --inlineSourceMap or --sourceMap properties to be set /* other options */ "experimentalDecorators": "EmitDecoratorMetadata ": true // provide metadata support for decorators}}Copy the code

If you want more, check out my list of 1.5K+ open source projects on Github: Awesome -typescript.

Github.com/semlinker/a…

15. Reference Resources

  • mariusschulz – the-unknown-type-in-typescript
  • In-depth understanding of typescript-compilation context