What is a TypeScript

  • TypeScript is JavaScript with a type system added for projects of any size

  • TypeScript is a static, weakly typed language

  • TypeScript is fully javascript-compatible and does not modify JavaScript runtime features

  • TypeScript can be compiled to JavaScript and run in a browser, Node.js, or any other environment that can run JavaScript

  • TypeScript has many compilation options, and it’s up to you to be as strict as you want with type checking

  • TypeScript can coexist with JavaScript, which means that JavaScript projects can migrate to TypeScript gradually

  • TypeScript enhances the editor (IDE) by providing code completion, interface hints, jump to definitions, code refactoring, and more

  • TypeScript has an active community, and most common third-party libraries provide type declarations

  • TypeScript keeps pace with standards, meeting the latest ECMAScript standards (Stage 3)

TypeScript type safety

Type safety, that is, using types to prevent programs from doing things that are invalid (in different statically typed languages, invalidation can mean either a run-time program crashes or it doesn’t crash but does something that is meaningless)

Here are a few examples of inefficiencies:

  • A number times a list

  • A call to a function that takes a list of objects but passes in a string

  • Call a method on an object that does not exist

These problems often occur in programming. Programming languages try to determine your true intent when they see you doing something that doesn’t work. Take JavaScript for example:

3 + []        // Result is "3"

const obj = {}
obj.test      // The result is undefined

function x(a) {
    return a/2
}
a('y')        // The result is NaN
Copy the code

Obviously, JavaScript does not report an error when it encounters an invalid operation, but rather inferences the result, which can cause a lot of trouble for later updates and maintenance

TypeScript’s job is to send an error message to the text editor while typing code. Here is the error message from the above example:

3 + []        // Operator '+' cannot be applied to type 'number' and 'never[]'

const obj = {}
obj.test      // Attribute 'test' does not exist on type '{}'

function x(a) {
    return a/2
}
a('y')        //'y' is not a numeric type
Copy the code

Type is introduced

any

Any is the godfather of type. In TypeScript, everything is typed at compile time, but never use any unless absolutely necessary. If you and TS cannot determine the type, default to any. This is the bottom of the pocket type and should be avoided as much as possible.

Values of type any are just like regular JS, and the type checker is completely useless.

let a:any = Awesome!        //any
let b:any = 'yyds'     //any
let c:any = a + b      //any
Copy the code

The third statement would normally report an error, but it doesn’t, because we tell TS to add both values of type any. If you use any, make sure you annotate it explicitly.

unknown

In rare cases, if you really can’t predict the type of a value, use unknown instead of any. Like any, unknown stands for any value, but TS asks you to double check and refine the type.

Unknown types of values can be compared (use = = = = =, | |, && and?) Can be denied (!) , can be refined using js typeof and instanceof operators, the following are examples of unknown types:

let a:unknown = 30      //unknown
let b = a === 123       //boolean
let c = a + 10          // The object is of type "unknown"
if(typeof a === 'number') {
    let d = a + 10      //number
}
Copy the code

Conclusion:

1.TS does not derive any value as unknown and must display an annotation (for example above a)

2. Values of the unknown type can be compared (b)

3. Do not assume that a value of unknown type is of a certain type (c). You must prove to TS that a value is of a certain type (d).

boolean

Boolean types have two values: true and false. The types of values can be compared (using the = = = = =, | |, && and?) Can be denied (!) , the following are examples of Boolean types:

let a = true            //boolean
var b = false           //boolean
const c = true          //true
let d:boolean = true    //boolean
let e:true = true       //true
let f:true = false      // Cannot assign type "false" to type "true"
Copy the code

The example above shows that we can tell TS that a value is of type Boolean in several ways:

Boolean (a and b);

2. TS can derive the value as a specific Boolean value (c).

3. Can explicitly tell TS value type Boolean (d)

4. TS can be explicitly told to be a specific Boolean value (e and f).

E is not a normal Boolean type, but a Boolean type that is true only. Setting the type to a value restricts e and f from taking the specified value of all Boolean values. This feature is called a type literal (a type that represents only one value)

number

Number includes all numbers: integer, floating point, positive, negative, Infinity, NaN, and so on. Of course, numbers can also perform arithmetic operations: add (+), subtract (-), multiply (*), divide (/), modulus (%), compare (<, >), etc. The following is an example of the number type:

let a = 123                 //number
var b = Infinity*0.10       //number
const c = Awesome!               / / 666
let d = a < b               //boolean
let e:number = 100          //number
let f:3.1415926 = 3.1415926 / / 3.1415926
let g:3.14 = Awesome!            // Cannot assign type '666' to type '3.14'
Copy the code

Like Boolean, there are four ways to declare a value as number:

1. Let TS derive values of type number (a and b)

2. Use const TS to derive a specific number (c)

3. Can explicitly tell TS value type is number (e)

4. The value of TS can be explicitly told to be a specific number (f and g)

bigint

Bigint is a new type introduced by JS and TS to handle large integers without worrying about rounding errors. The largest integer of type number is 2^53^, and bigint is much larger than that. The bigInt type contains all bigInt numbers and supports addition, subtraction, multiplication, division, and comparison. The following is an example of bigint:

let a = 12345n               //bigint
const b = 56789n             //56789n
var c = a + b                //bigint
let d = a < 123              //boolean
let e = 66.6n                //bigint Text must be an integer
let f:bigint = 100n          //bigint
let g:100n = 100n            //100n
let h:bigint = 100           // Can't assign type '100' to type 'bigint'
Copy the code

Declaring bigint is similar to Boolean and number.

string

String contains all strings and operations that can be performed on strings, such as concatenating strings (+), slicing (.slice), and so on. The following is an example of string usage:

let a = 'hello mwq'            //string
var b = 'mwqYYDS'              //string
const c = '? '                  / / '? '
let d = a + ' ' + b + c         //string
let e:string = 'zoom'          //string
let f:'mwq' = 'mwq'            //'mwq'
let g:'mwq' = 'qwm'            // Can't assign type 'QWM' to type 'MWQ'
Copy the code

Declaring string is similar to declaring Boolean and number.

symbol

Symbol is often used in place of string keys for objects and maps, ensuring that the correct known keys are used in case keys are accidentally set.

let a = Symbol('a')          //symbol
let b:symbol = Symbol('b')   //symbol
var c = a === b              //boolean
let b = a + 'x'              // The "+" operator cannot be applied to type "symbol"
Copy the code

Symbol(‘a’) creates a new Symbol with the specified name. This Symbol is unique and not equal to any other Symbol(compare using == or ===).

Similarly, symbols can be declared as unique symbols:

const e = Symbol('e')                      //typeof e
const f:unique symbol = Symbol('f')        //typeof f
let g:unique symbol = Symbol('f')          // Variables of type "unique symbol" must be const
let h = e === e                            //boolean
Copy the code

An array of

TypeScript manipulates array elements just like JavaScript. There are two ways to define an array. First, the element type can be followed by [] to represent an array of elements of that type:

let arr: number[] = [1.2.3]             //number[]
Copy the code

The second way is to use Array generics, Array< element type > :

let list: Array<number> = [1.2.3]       //number[]
Copy the code
const a = [2.'a']                         // (string|number)[]
let b = []                                //any[]
b.push(1)                                 //number[]
b.push('red')                             //(string|number)[]
let c:number[] = []                       //number[]
c.push(1)                                 //number[]
c.push('red')                             // Parameters of type "string" cannot be assigned to parameters of type "number"
Copy the code

As we can see from a above, declaring an array with const does not cause TS to derive a narrower range of types.

Think about why b doesn’t get an error?

Let’s do another example and you’ll see

const buildArray = () = > {
    let a =[]                       //any[]
    a.push(1)                       //number[]
    a.push('a')                     //(string|number)[]
    return a 
}

let myArray = buildArray()          //(string|number)[]
myArray.push(true)                  / / type "Boolean" type of parameters cannot be assigned to "string | number" parameters
Copy the code

When initializing an empty array, TS does not know the type of the elements in the array, so it deduces the type any. After adding elements to the array, TS begins to piece together the array’s type. When the array leaves the defined scope, TS finally determines a type and does not expand.

tuples

A tuple is a subtype of Array. It is a special way to define an Array. The length is fixed, and the values at each index bit have a fixed known type. The annotation type must be displayed when declaring tuples, because the syntax used to create tuples is the same as for arrays (square brackets []), and TS encounters square brackets to deduce the type of the array.

let a: [string.number]
a = ['hello'.10]                   // OK
a = [10.'hello']                   // Error
Copy the code

Tuples also support optional elements:

let b:[number.number? ] [] = [[2333],
    [23.33]]Copy the code

Tuples also support residual elements, which define a minimum length for a tuple:

let name:[string. string[]] = ['mwq'.'qwm'.'wqm'.'mqw']      // A list of strings with at least one element
let list:[number.boolean. string[]] = [1.false.'a'.'b']     // Lists of different element types
Copy the code

Declaring read-only arrays

TS natively supports read-only array types for creating immutable arrays. To create a read-only array, display the annotation type; To change read-only arrays, use non-morphing methods such as.concat and.slice, not mutable methods such as.push and.splice.

Declare read-only arrays and tuples as follows:

type A = readonly string[]           //readonly string[]
type B = ReadonlyArray<string>       //readonly string[]
type C = Readonly<string[] >//readonly string[]

type D = readonly [number.string]   //readonly [number, string]
type E = Readonly<[number.string] >//readonly [number, string]
Copy the code

The enumeration

Enumerated types are a complement to JavaScript’s standard data types. As in other languages such as C#, enumeration types can be used to give friendly names to a set of values.

There are two kinds of enumerations: string-to-string mappings and string-to-number mappings. As follows:

enum Language {
    English,
    Spanish,
    Russian
}
Copy the code

TS can automatically derive numbers for each member of the enumeration, or you can set them manually. As follows:

enum Language {
    English = 2,
    Spanish = 5,
    Russian = 8
}
Copy the code

By convention, enumeration names are uppercase singular. Keys in enumerations are also uppercase

Values in enumerations are accessed using either dot or square bracket notation

let myFirstLanguage = Language.Russian
let mySecondLanguage = Language['English']
Copy the code

An enumeration can be divided into several declarations, which TS will automatically merge. Note that if you declare enumerations separately, TS can only deduce some of the values, so it is best to display assignments for each member of the enumeration

enum Language {
    English = 0,
    Spanish = 1,}enum Language {
    Russian = 2
}
Copy the code

The values of the members can be computed and do not have to be assigned to all members (TS automatically deduces their values) :

enum Language {
    English = 2,
    Spanish = 200+20,
    Russian            / / 221
}
Copy the code

Enumeration values can also be strings, or even a mixture of strings and numbers:

enum Color {
    Red = '#c10000',
    Blue = '#007ac1',
    Pink = 0xc10050.// Hexadecimal literals
    White = 255          // Decimal literals
}

let red = Color.Red      //'#c10000'
let white = Color[255]   //White
Copy the code

As you can see above, TS allows access to enumerations through both values and keys

void

In a way, void is like the opposite of any; it means there is no type at all. When a function returns no value (such as console.log), you will usually see the return value type void:

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

Declaring a void variable doesn’t do much good, because you can only assign undefined and null to it:

let unusable: void = undefined
Copy the code

Null, and undefined

In TypeScript, undefined and null have their own types called undefined and NULL, respectively. Like void, their own types are not very useful:

let a: undefined = undefined;
let b: null = null
Copy the code

By default null and undefined are subtypes of all types. This means that you can assign null and undefined to variables of type number.

never

The never type represents the types of values that never exist. For example, the never type is the return type of function expressions or arrow function expressions that always throw an exception or have no return value at all.

const a:never = () = > {
    throw TypeError('I always error')}const b:never = () = > {
    while(true) {
        doSomething()
    }
}
Copy the code

If unknown is the parent of every other type, then never is the child of every other type. We can think of never as a “bottom pocket type.” This means that we can assign the never type to any other type.

object

There are several ways to use types to describe objects in TS, which I’ll describe in the following sections.

Object literal syntax

let a:{b:number} = {
    b:2333
}                          //{b:number}


const a:{b:number} = {
    b:2333
}                          //{b:number}
Copy the code

It is worth noting that declaring an object using const does not narrow the derived type

Small test:

Let’s see what types can be assigned to A?

let a:{
    b:number, c? :string,
    [key:number] :boolean
}

a = {b: 1}
a = {b: 1.c: undefined}
a = {b: 1.c: 'd'}
a = {b: 1.10: true}
a = {b: 1.10: true.20: false}
Copy the code

The index sign

The [key:T] :U syntax is called index signatures. This is the way we tell TS that the specified object may have more keys. The syntax says, “In this object, keys of type T correspond to values of type U.”

Note:

1. The key type (T) must be assignable to either number or string

2. The key name in the index signature can be any word, not necessarily key

object

Object represents a non-primitive type, that is, a type other than number, string, Boolean, symbol, NULL, or undefined.

Use the object type to better represent apis like Object.create. Such as:

interface ObjectConstructor {
  create(o: object | null) :any;
}

const proto = {};

Object.create(proto);     // OK
Object.create(null);      // OK
Object.create(undefined); // Error
Object.create(1337);      // Error
Object.create(true);      // Error
Object.create("oops");    // Error
Copy the code

Empty object type {}

Any type other than null and undefined can be assigned to an empty object type, which is complicated to use and should be avoided

let danger:{}
danger = {}                
danger = {x:1}
danger = []
danger = 2
Copy the code

Object

With {} function basically the same, it is best to avoid using, not here

Type the alias

We can alias values using variable declarations (let, const, and var). Similarly, we can alias types as shown in the following example:

type Message = string | string[];

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

Type aliases help reduce the need to re-enter complex types and make it clearer what a variable does (with descriptive type names).

TypeScript assertion

Types of assertions

Sometimes you’ll find that you know more about a value than TypeScript does. Usually this happens when you clearly know that an entity has a more exact type than its existing type.

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

Type assertions come in two forms:

1. Angle bracket syntax

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

2. As the grammar

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

Not empty assertion

A new postfix expression operator in context when the type checker cannot determine the type! Can be used to assert that operation objects are non-null and non-undefined. Specifically, x! Null and undefined are excluded from the x range. Examples are as follows:

1. Ignore null and undefine types

function myFunc(maybeString: string | undefined | null) {
  const onlyString: string = maybeString; // Error
  const ignoreUndefinedAndNull: string= maybeString! ;// Ok
}
Copy the code

2. Ignore the undefine type when calling a function

type NumGenerator = () = > number;

function myFunc(numGenerator: NumGenerator | undefined) {
  const num1 = numGenerator(); // Error
  constnum2 = numGenerator! (a);//OK
}
Copy the code

Determine the assignment assertion

In TypeScript version 2.7, deterministic assignment assertions are introduced, which allow instance attributes and variable declarations to be placed after one! To tell TypeScript that the property is explicitly assigned. To better understand what it does, let’s take a concrete example:

let x: number;
initialize();
// Variable 'x' is used before being assigned.(2454) 
// Cannot redeclare block scope variable "x"
console.log(2 * x); // Error

function initialize() {
  x = 10;
}
Copy the code

The exception message clearly indicates that the variable x was used before the assignment. To resolve this problem, we can use a deterministic assignment assertion:

letx! :number;
initialize();
console.log(2 * x); // Ok

function initialize() {
  x = 10;
}
Copy the code

Through the let x! : number; Determine the assignment assertion, and the TypeScript compiler knows that the property is explicitly assigned.

The joint type

The joint type

The value represented by a union type may be one of many different types. For example, A | B joint type of A value is likely to be A type, may also be A type B.

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

sayHello('mwq');
sayHello(null);
Copy the code

In addition, for union types, you might encounter the following usages:

let num: 1 | 2 = 1;
type Cap = "butt" | "round" | "square";
Copy the code

Identifiable union types

The essence of this type is a type protection approach that combines union and literal types. If a type is a combination of multiple types, and multiple types have a common property, then different type-protected blocks can be created using this common property. It consists of three main points: recognizability, union type, and type guard.

  1. Can be identified

Recognizability requires that each element in the union type contain a singleton type attribute:

enum CarTransmission {
  Automatic = 200,
  Manual = 300
}

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

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

interface Truck {
  vType: "truck"; 
  capacity: number; // tons
}
Copy the code

In the code above, we define the Truck, Car, and Truck interfaces, and each of these interfaces contains a vType attribute called an identifiable attribute, while the other attributes are associated only with the attribute’s interface.

  1. The joint type

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

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

We are now ready to use the Vehicle union type, which can represent different types of vehicles for variables of the Vehicle type.

  1. Type the guards

Let’s define a evaluatePrice method used to calculate prices based on vehicle type, capacity, and evaluation factors 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 display the following error message:

Property 'capacity' does not exist on type 'Vehicle'.      // There is no property "capacity" on type "Vehicle".
Property 'capacity' does not exist on type 'Motorcycle'.   // Attribute 'capacity' does not exist on type 'Motorcycle'
Copy the code

The reason is that the Capacity attribute does not exist in the Motorcycle interface, nor does it exist in the Car interface. So how do we solve these problems now? At this point, we can use type guards. Let’s refactor the evaluatePrice method 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":
      returnvehicle.make * EVALUATION_FACTOR; }}Copy the code

In the above code, we use the switch and case operators to implement type guarding to ensure that in the evaluatePrice method, we can safely access the property contained in the Vehicle object to calculate the correct price for the vehicle type.

Cross type

Cross-typing in TypeScript combines multiple types into a single type. The & operator allows you to superimpose existing types into a single type that contains all of the required characteristics of the type.

type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };

let point: Point = {
  x: 1.y: 1
}
Copy the code

Merge of base type attributes of the same name

What happens if, in the process of merging multiple types, some types have the same members, but the corresponding types are inconsistent, as in the following example?

interface X {
  c: string;
  d: string;
}

interface Y {
  c: number;
  e: string
}

type XY = X & Y;
Copy the code

Interface X and interface Y both have the same attribute C, but they are of different types. Let’s take a look:

let p:XY = {
      c:6.// Cannot assign type "number" to type "never"
      d:'d'.e:'e'
    }

let q:XY = {
      c:'c'.// Cannot assign type "string" to type "never"
      d:'d'.e:'e'
    }
Copy the code

Oops, property C is of type never. Let’s see why. After crossing, the type of attribute C is string & number. That is, the type of attribute C can be either string or number. Obviously, this type does not exist, so the type of attribute C after crossing is never.

A merge of non-base type attributes of the same name

In the example above, the type of the internal member C of interface X and interface Y are both basic data types, so what happens if they are non-basic data types? Let’s look at a concrete example:

interface D { d: boolean; }
interface E { e: string; }
interface F { f: number; }

interface A { x: D; }
interface B { x: E; }
interface C { x: F; }

type ABC = A & B & C;

let abc: ABC = {
  x: {
    d: true.e: 'semlinker'.f: Awesome!}};console.log('abc:', abc);
Copy the code

Let’s have a look at the print:

Therefore, when mixing multiple types, the same member can be successfully merged if the member type is not a basic data type.

Type the guards

Type protection is an expression for an executable runtime check to ensure that the type is within a certain range. There are four main ways to implement type protection:

Type judgment:typeof

function test(input: string | number) {
  if (typeof input === 'string') {
    // The type of input "tightened" is string
  } else {
    // The input type is tightened to number}}Copy the code

Typeof type protection supports only two types: typeof V === “typename” and typeof V! == typename, “typename” must be “number”, “string”, “Boolean” or “symbol”.

Example judgment:instanceof

class Foo {}
class Keep {}

function test(input: Foo | Keep) {
  if (input instanceof Foo) {
    // Where input is of type "tightened" to Foo
  } else {
    // Here the input type "tighten" is Keep}}Copy the code

Attribute judgment:in

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

Custom guard

The syntax structure of a custom guard is {parameter} is {type}. Since the nature of a custom guard is a type assertion, you can use any logic to determine the type in a custom guard function.

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

The TypeScript function

Declare and call functions

TS and JS functions:

There are many operations that can be performed on functions in JS: they can be passed as arguments to other functions; Can be the return value of a function; Can be assigned to objects and stereotypes; You can assign attributes; You can read properties and so on. TS continues this tradition with a rich type system.

Normally, we display the arguments to the annotation function. TS can deduce the types in the function body, but in most cases cannot deduce the types of the parameters. The return type can be derived, but annotations can also be displayed:

function add(a: number, b: number) :number {
    return a + b
}
Copy the code

The above example uses the named function syntax, but JS and TS support at least five other ways to declare functions:

// A named function
function greet(name: string) {
    return 'hello' + name
}

// Function expression
let greet2 = function(name: string) {
    return 'hello' + name
}

// Arrow function expression
let greet3 = (name: string) = > {
    return 'hello' + name
}

// Short form of arrow function expression
let greet4 = (name: string) = > 
    return 'hello' + name

// Function constructor
let greet5 = new Function('name'.'return "hello" + name')
Copy the code

With the exception of function constructors, the other syntax can be safely used in TS to ensure type safety.

Why are function constructors unsafe? Enter the example in the editor and you will see that it is of type Function (as shown below). This is a callable object (that is, it can be followed by ()) and has all the prototype methods of function.prototype. But there is no indication of the types of arguments and return values, so you can call the function with any argument.

When you call a function in TS, you simply pass in the argument, and TS checks whether the argument is compatible with the type of the function parameter.

add(1.2)       / / 3
greet('mwq')    //'hello mwq'
Copy the code

Function types

Ts also has function types, which describe a function. This syntax is also called calling signature or type signature:

type FnType = (x: number, y: number) = > number
Copy the code

The full function is written:

let myAdd: (x: number, y: number) = > number = function(x: number, y: number) :number {
  return x + y
}

// Use the FnType
let myAdd: FnType = function(x: number, y: number) :number {
  return x + y
}

// ts automatically deduces the parameter type
let myAdd: FnType = function(x, y) {
  return x + y
}
Copy the code

Optional and default parameters

Optional parameters

Like objects and tuples, you can use? Mark the parameters as optional. When declaring function arguments, the required ones precede the optional ones:

function log(message: string, userId? :string) {
    let time = new Date().toLocaleTimeString()
    console.log(time, message, userId || 'Not signed in')
}

log('eat')              // 2:38:19 PM eat Not signed in
log('game'.'mwq')      // 2:38:19 PM game MWQ
Copy the code

The default parameters

As with JS, you can provide default values for optional arguments. This eliminates the need to pass in the value of the argument when called.

function log(message: string, userId = 'Not signed in') {
    let time = new Date().toLocaleTimeString()
    console.log(time, message, userId)
}

log('eat')              // 2:38:19 PM eat Not signed in
log('game'.'mwq')      // 2:38:19 PM game MWQ
Copy the code

The remaining parameters

Required, default, and optional parameters have one thing in common: they represent a parameter. Sometimes, you want to manipulate multiple arguments at once, or you don’t know how many arguments will be passed in. In JavaScript, you can use arguments to access all incoming arguments. In TypeScript, you can collect all arguments into a single variable:

function buildName(firstName: string. restOfName:string[]) {
  return firstName + "" + restOfName.join("");
}

let employeeName = buildName("Joseph"."Samuel"."Lucas"."MacKinzie");
Copy the code

The remaining parameters are treated as an unlimited number of optional parameters. You could have none, you could have any of them.

Comment on the type of this

There is now a utility function that formats the date as follows:

function fancyDate(this:Date) {
    console.log(`The ${this.getDate()}/The ${this.getMonth()}/The ${this.getFullYear()}`);
}

fancyDate.call(new Date)       / / 1/8/2021
Copy the code

If the function uses this, declare the type of this in the first argument to the function. If not, the editor will report an error:

function fancyDate(this:Date) {
    console.log(`The ${this.getDate()}/The ${this.getMonth()}/The ${this.getFullYear()}`);
    //"this" implicitly has type "any" because it has no type annotation
}
Copy the code

Function overloading

In most programming languages, once a particular parameter and return type are specified when declaring a function, the function can only be called with the corresponding parameter, and the return value type is always the same. JS is a dynamic language, is bound to need a variety of ways to call a function method. Not only that, but sometimes the type of the output depends on the type of the input parameter.

TS supports dynamically overloaded function (function with multiple calling signatures) declarations for type safety. The following is an example:

// Override signature (function type definition)
function toString(x: string) :string;
function toString(x: number) :string;

// Implement signature (function body concrete implementation)
function toString(x: string | number) {
  return String(x)
}

let a = toString('hello') // ok
let b = toString(2) // ok
let c = toString(true) // error
Copy the code

The function implementation signature is not part of the overload, and only the overload is visible when there is at least one signature of the function overload. The last implementation signature does not contribute to the shape of the signature, so to get the desired behavior, you need to add an extra overload:

function toString(x: string) :string;

function toString(x: number) :string {
  return String(x)
}

toString(2) // error
Copy the code

interface

The interface profile

One of TypeScript’s core tenets is type-checking of the structure a value has. It is sometimes called “duck type discrimination” or “structural typing”. In TypeScript, interfaces name these types and define contracts for your code or third-party code. Like a type alias, an interface is a way to name a type without having to define it in a line.

The difference between interface and type aliases

  1. Both can be used to describe the type of an object or function, but the syntax is different
type mwq = {
    name:string.age:number.alive:boolean
}
Copy the code

This type alias can be rewritten as the following interface:

interface mwq {
    name:string.age:number.alive:boolean
}
Copy the code
  1. Both are extensible, but the syntax is different. Also, note that interface and type aliases are not mutually exclusive. Interfaces can extend type aliases and vice versa

interface extends interface

interface PartialPointX { x: number }
interface Point extends PartialPointX { y: number }
Copy the code

type alias extends type alias

type PartialPointX = { x: number }
type Point = PartialPointX & { y: number }
Copy the code

interface extends type alias

type PartialPointX = { x: number }
interface Point extends PartialPointX { y: number }
Copy the code

type alias extends interface

interface PartialPointX { x: number }
type Point = PartialPointX & { y: number }
Copy the code
  1. Type alias is more general, the right can be any type, including the expression type (type, plus & or | type operators etc.); In interface declarations, the right-hand side must be a structure. The following example cannot be overridden with an interface:
type A = number
type B = A | string
Copy the code
  1. When extending an interface, TS checks whether the extended interface can be assigned to the extended interface. Such as:
interface A {
    good(x:number) :string
    bad(x:number) :string
}

interface B extends A {
    good(x:number) :string
    bad(x:string) :string    // Interface B is incorrect extension interface A.
                            // The type of property "bad" is incompatible.
                            // Cannot assign type "(x: string) => string" to type "(x: number) => string".
                            // The type of parameter "x" and "x" are incompatible.
                            // Cannot assign type "number" to type "string"
}                              
Copy the code
  1. Multiple interfaces of the same name in the same scope are automatically merged; Multiple type identifiers of the same name in the same scope cause compilation errors.
interface Point { x: number; }
interface Point { y: number; }

const point: Point = { x: 1.y: 2 };
Copy the code

Object type

Interfaces in TypeScript are a very flexible concept and are often used to describe objects:

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

let mwq: Person = {
  name: "mwq".age: 23};Copy the code

Read-only and optional properties

Read-only property

Some object properties can only change their value when the object is created. You can specify read-only properties with readonly before the property name:

interface Point {
    readonly x: number;
    readonly y: number;
}

let p1: Point = { x: 10.y: 20 
                };

p1.x = 5; // error!
Copy the code

When setting read-only properties, when to use const and when to use readonly?

Optional attribute

Not all attributes in the interface are required. Some exist only under certain conditions, or not at all.

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

class

class

The following is an example:

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting; }}let greeter = new Greeter("world");
Copy the code

We declare a Greeter class. This class has three members: a property called greeting, a constructor, and a greet method.

You’ll notice that we use this when referring to any of the class members. It means that we are accessing a member of the class.

In the last line, we construct an instance of the Greeter class using new. It calls the previously defined constructor, creates a new object of Greeter type, and initializes it by executing the constructor.

inheritance

In TypeScript, we can use common object-oriented patterns. One of the most basic patterns in class-based programming is to allow the use of inheritance to extend existing classes.

class Person {
  public love: string;
  constructor(love: string) {
    this.love = love;
  }
  public sayLove() {
    console.log(`my love is The ${this.love}`)}}class SuperPerson extends Person {
  public name: string;
  constructor(love: string, name: string) {
    super(love);
    this.name = name;
  }
  public sayName(){
    console.log(`my name is The ${this.name}`)}}let me = new SuperPerson('HTML'.'mwq');
me.sayLove()
me.sayName()
Copy the code

We must call super() before accessing the this property in the constructor. This is an important rule that TypeScript enforces.

Public, private, and protected modifiers

public

Decorated properties or methods are common and accessible anywhere. In TypeScript, members are public by default.

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

private

When a member is marked private, it cannot be accessed outside the class in which it is declared, but only within its own class.

class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }}new Animal("Cat").name; // Error: 'name' is private.
Copy the code

protected

Modified properties or methods are protected and accessible in this class and subclasses.

class Person {
    protected name: string;
    constructor(name: string) { this.name = name; }}class Employee extends Person {
    private department: string;

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

    public getElevatorPitch() {
        return `Hello, my name is The ${this.name} and I work in The ${this.department}. `; }}let howard = new Employee("Howard"."Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); / / error

Copy the code

Static attributesstatic

Properties that use static modifiers are accessed by classes and are common to each instance. Methods that use static modifiers are called class methods and can be called directly by classes

class Parent {
    static species: string = 'human'
    public name: string
    private money: number
    constructor(name: string) {
        this.name = name 
    }
    eat() {
        console.log(`The ${this.name}At dinner `)}}console.log(Parent.species)     //'human'
Copy the code

readonlyThe modifier

Adding readOnly to the property ensures that it is read-only and cannot be modified. If there is a static modifier, write it after it

class Parent {
    static readonly species: string = 'human'
    public name: string
    private money: number
    constructor(name: string) {
        this.name = name 
    }
    eat() {
        console.log(`The ${this.name}At dinner `)
    }
}
Parent.species = 'dog'  // "species" cannot be assigned because it is read-only
Copy the code

An abstract classabstract

Classes declared using the abstract keyword are called abstract classes. An abstract class cannot be instantiated because it contains one or more abstract methods. An abstract method is one that contains no concrete implementation

abstract class Person {
  constructor(public name: string){}

  abstract say(words: string) :void;
}

const lolo = new Person();    // Cannot create an instance of an abstract class
Copy the code

Abstract classes cannot be instantiated directly; we can only instantiate subclasses that implement all abstract methods. The details are as follows:

abstract class Person {
  constructor(public name: string){}

  // Abstract methods
  abstract say(words: string) :void;
}

class Developer extends Person {
  constructor(name: string) {
    super(name);
  }
  
  say(words: string) :void {
    console.log(`The ${this.name} says ${words}`); }}const lolo = new Developer("mwq");
lolo.say("I love ts!");     // mwq says I love ts!
Copy the code

Class method overloading

In the previous section, we covered function overloading. It also supports overloading for class methods. The following is an example:

class ProductService {
    getProducts(): void;
    getProducts(id: number) :void;
    getProducts(id? :number) {
      if(typeof id === 'number') {
          console.log('gets the ID as${id}Product information ');
      } else {
          console.log('Get all product information'); }}}const productService = new ProductService();
productService.getProducts(Awesome!); // Get product information with id 666
productService.getProducts(); // Get all product information
Copy the code

The generic

A generic type is like a placeholder for a variable, and when used we can pass in the defined type as a parameter and output it as if it were a parameter

Generic syntax

Take a look at the following example:

function getValue(params: number) :number {
    return params
}
Copy the code

In the above example, I want to return a value, which is typed as number, but is not necessarily number in practice. We can solve this problem by generics, as shown in the following example:

function getValue<T> (params: T) :T {
    return params
}
Copy the code

Where T acts as a placeholder, adding

after methods (variables, interfaces, etc.)

If we need to pass in more than one parameter, look at the following example:

function getValue<T.U> (params: T, message:U) :T {
    console.log(message)
    return params
}

getValue<number.string> (1.'1')
Copy the code

A generic interface

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

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