I. Environment construction and compilation execution

TypeScript programs don’t run directly in a browser; we need to compile TypeScript code into JavaScript code through the TypeScript compiler.

Environment set up

Installation node. Js

TypeScript’s compiler is based on Node.js, so we need to install Node.js: the official website of Node.js

After the installation is complete, node can be called from a terminal or command-line tool such as CMD:

#View the current Node version
node -v
Copy the code

Install the TypeScript compiler

Install the TypeScript compiler using the NPM package management tool:

NPM i-g typescript // or yarn add -g typescriptCopy the code

After the installation is complete, we can invoke the compiler with the TSC command:

#View the current TSC compiler version 
tsc -v
Copy the code

Win10 runtscAn error

Run PowerShell as the administrator and set the set-ExecutionPolicy RemoteSigned

Compile the code

By default, TypeScript files have a.ts suffix

// ./src/test.ts
let str: string = 'una';

export {};
Copy the code

In TypeScript, as in ECMAScript 2015, any file that contains a top-level import or export is treated as a module. Conversely, if a file does not have a top-level import or export declaration, its contents are considered globally visible.

Compile.ts files using our installed TypeScript compiler TSC: tsc. / SRC /test.ts. By default, a JS file with the same name is generated in the current file directory.

Tsc. / SRC /test.ts // Compile files in the SRC folderCopy the code

Common compilation options

  • –outDir: Specifies the output directory for compiled files
TSC --outDir./dist./ SRC /test.ts // Compile files in the dist folderCopy the code

  • –target: Specifies the code version target to compile. The default is ES3
tsc --outDir ./dist --target ES6 ./src/test.ts
Copy the code

  • — Watch: Runs in listening mode and automatically compiles when the file changes
tsc --outDir ./dist --target ES6 --watch ./src/test.ts
Copy the code

The examples above give you an idea of how TSC works, but as you can see, typing in a bunch of options every time you compile can be tedious. TypeScript compilation provides a more powerful and convenient way to compile configuration files: Tsconfig. json, we can save the above compilation options to this configuration file.

Compiling configuration files

The tsconfig.json file is automatically loaded when the TSC command is executed. The configuration file format is as follows:

// ./tsconfig.json
{
    // Compile options
    "compilerOptions": { 
        // js output directory
        "outDir": "./dist".// Compile the output to the target ES version, ES3 by default
        "target": "ES2015".// Listen for changes
        "watch": true,},// The module to compile
    // ** : all directories (including subdirectories)
    // * : all files. You can also specify type *.ts
    "include": ["./src/**/*"] // Relative path
}
Copy the code

With a separate configuration file, we can run TSC directly.

Specify the configuration file to load

// ./configs/tsconfig.json
{
    // Compile options
    "compilerOptions": { 
        // js output directory
        "outDir": ".. /dist".// Compile the output to the target ES version, ES3 by default
        "target": "ES2015".// Listen for changes
        "watch": true,},// The module to compile
    // ** : all directories (including subdirectories)
    // * : all files. You can also specify type *.ts
    "include": [".. /src/**/*"] // Relative path
}
Copy the code

Delete the./tsconfig.json file and create a new configuration file. Note that the relative path has been changed.

Using –project or -p to specify the configuration file directory will load the tsconfig.json file in that directory by default:

tsc -p ./configs
Copy the code

You can also specify a specific configuration file and change the name of the configuration file to ts.json:

tsc -p ./configs/ts.json
Copy the code

Type system initialization

Type system composition

The type system consists of two important components:

  • Type annotation (definition, annotation) – typing
  • Type-checking – type-checking

Type annotation

Type annotation is to add type description to data (variable, function (parameter, return value)) in code. When a variable or function (parameter) is annotated, the type that does not conform to the annotation type cannot be stored.

With annotations, the TypeScript compiler can check the data for type-validity based on the annotations, and various editors, ides, and so on can intelligently hint.

Type test

As the name suggests, it checks the type of data. Notice that the emphasis here is on the word type.

Type system to detect types, not specific values (although, sometimes also can readings), such as the scope of a parameter (1-100), we can’t depend on the type system to complete the test, it should be our business logic, type system test is whether its value type for the digital!

Type annotation

In TypeScript, the basic syntax for type annotations is: Data carrier: type.

Basic types (String, number, Boolean)

Basic types include string, number, and Boolean.

  • Boolean value Boolean

    let isDone: boolean = false;
    Copy the code
  • Digital number

    let decLiteral: number = 6;
    Copy the code

    Like JavaScript, all numbers in TypeScript are floating point numbers. In addition to supporting decimal and hexadecimal literals, TypeScript also supports binary and octal literals introduced in ECMAScript 2015.

    let hexLiteral: number = 0xf00d;
    let binaryLiteral: number = 0b1010;
    let octalLiteral: number = 0o744;
    Copy the code
  • String let name: string = “Bob “;

    Template string: surrounded by backquotes (‘), expressed as ${expr} embedded. You can define multi-line text and inline expressions.

    let name: string = `una`;
    let age: number = 23;
    let sentence: string = `Hello, my name is ${ name }.
    
    I'll be ${ age + 1 } years old next month.`;
    Copy the code

Type inference: If a variable is initialized without an identifier, the type is automatically recognized.

Null and undefined types

In TypeScript, undefined and null have their own types called undefined and NULL, respectively. The two types have one and only one value, and when a variable is labeled Null and Undefined, it cannot be modified.

let a: null; 
a = null; // ok 
a = 1; // The error Null type cannot be modified once the standard
Copy the code

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

let a: number;
a = null; // ok 
Copy the code

If a variable is declared but not assigned, the value is undefined, but if it is also untyped, the default type is any.

Because null and undefined are subtypes of other types, there are some hidden problems by default:

let a:number; 
a = null; Can't assign type 'null' to type 'number'
a.toFixed(1); // ok (no error, but the actual operation is problematic)
Copy the code

However, when you specify strictNullChecks, null and undefined can only be assigned to void and themselves. This avoids many common problems.

// ./tsconfig.json
{
    "compilerOptions": {
        "outDir": "./dist"."target": "es5"."watch": true."strictNullChecks": true // Strict null checking
    },
    "include": ["./src/**/*"]}Copy the code
let a: number; 
a = null;
a.toFixed(1); // error 
Copy the code

Note: TypeScript encourages use of strictNullChecks wherever possible.

An array type

The types of arrays stored in TypeScript must be consistent, so when annotating array types, we also annotate the data types stored in arrays:

  • Generic annotation: Array< element type >

    // 
            
              represents the type of data stored in the array
            
    let arr1: Array<number> = []; 
    arr1.push(100); // ok 
    arr1.push('Hello.');// The error string cannot be assigned to arguments of type 'number'
    Copy the code
  • Simple annotation: An element type is followed by [] to represent an array of elements of that type

    let arr2: string[] = []; 
    arr2.push('una'); // ok 
    arr2.push(1); // error 
    Copy the code

A tuple type

  • A tuple represents an array with a known number and type of elements, which need not be of the same type.
  • Initialize theThe number of data andCorresponding position annotation typeMust be consistent
  • When an out-of-bounds (more than previously defined) element is accessed, the union type is used instead, but it must be one of the types in the tuple annotation.
// 1. Define a pair of tuples of type string and number
let x: [string.number]; // Declare a tuple type
x = ['hello'.10]; // OK
x = [10.'hello']; // The number of initialization data and the corresponding position annotation type must be consistent

// 2. When accessing an element with a known index, the correct type is obtained
console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'

// 3. When accessing an out-of-bounds element, the union type is used instead:
x[3] = 'world'; / / OK, strings can be assigned to (string | number) type
x[4] = true; / / Error, Boolean not (string | number) type
Copy the code

Enumeration Type (enum)

Enumerations are a way to organize the collection of related data. Enumerations allow us to give friendly names to groups of related data:

enum HTTP_CODE { 
    ERROR,
    OK = 200,
    NOT_FOUND = 404, 
    METHOD_NOT_ALLOWED 
};

HTTP_CODE.ERROR;// 0 The first enumeration value defaults to 0
HTTP_CODE.OK;/ / 200
HTTP_CODE.METHOD_NOT_ALLOWED;  // the non-first enumeration value is the previous numeric enumeration value + 1
HTTP_CODE.OK = 1; // The error enumeration value is read-only (constant) and cannot be modified after initialization
Copy the code

Matters needing attention:

  • Key cannot be a number;

  • A value can be a number, called an enumeration of numeric types, or a string, called an enumeration of string types, but cannot be anything else. The default value is a number: 0.

  • Enumeration values can be omitted, if omitted:

    • The first enumeration defaults to 0;
    • The non-first enumeration value is the previous numeric enumeration value + 1;
  • Enumeration values are read-only (constant) and cannot be modified after initialization;

  • If the previous enumeration value is of type string, subsequent enumerations must be manually assigned;

enum URLS { 
    USER_REGISETER = '/user/register', 
    USER_LOGIN = '/user/login'.// If the previous enumeration value is of type string, subsequent enumerations must be manually assigned
    INDEX = 0 
}
Copy the code

Enumeration names can be either uppercase or lowercase, and all uppercase is recommended (it is common to use all uppercase naming to denote values as constants).

Void type

Represents a type without any data. It is usually used to annotate the return value type of a function with no return value. The default annotation type of the function is void

function fn() :void { 
    // 没有 return 或者 return undefined 
}
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

StrictNullChecks is false, both undefined and null can be assigned to void, but strictNullChecks is true, Only undefined can be assigned to void.

Never type

The never type represents the types of values that never exist:

  • An exception is thrown
  • A function that returns no value
  • Arrow function
  • Never a true variable

The never type is a subtype of any type and can be assigned to any type; However, no type is a subtype of never, and even any cannot be assigned to never.

Here are some functions that return type never:

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

// The inferred return value type is never
function fail() {
    return error("Something failed");
}

// A function that returns never must have an unreachable end
function infiniteLoop() :never {
    while (true) {}}Copy the code

Any type (any)

Sometimes, we don’t know exactly what type the value is or we can label it as any without typing it:

let a: any;
Copy the code
  • A variableDeclare that no value is assigned and no type is markedThe default value isanyType;
  • Any type of value can be assigned toanyType;
  • anyA type can also be assigned to any type;
  • anyA type has arbitrary attributes and methods;

The annotation of type any also means abandoning type detection for the value and abandoning the IDE’s intelligent hints.

let a; // A variable declaration is unassigned and untyped. Default is type any. An error is reported when noImplicitAny is true.
let c: any = 'una'; The any type is a subtype of any type
let d: number = 1;
d.toFixed(1)

d = c; // assign c to d, and d becomes any
d.toFixed(1); // d becomes a string, there is no toFixed method, although the IDE does not report an error, but when running will report an error
Copy the code

When noImplicitAny is set to true, an error is reported when a function argument contains an implied any type.

Unknown type

Unknown, new to the security version of any in version 3.0, but different from any:

  • unknow Can only be assigned tounknow,any;
  • unknow There are no properties or methods;
let c: unknow = 'una'; 
let d: number = 1;
d.toFixed(1)

d = c; // Error unknow Can only be assigned to unknow and any
d.toFixed(1); // Error unknow has no properties or methods
Copy the code

Object type

Built-in object types

In JavaScript, there are many built-in objects, such as Object, Array, Date… We can annotate objects by constructor or class.

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

let a: object = {}; 
let arr: Array<number> = [1.2.3]; 
let d1: Date = new Date(a);Copy the code

Custom object types

Many times, we may need to customize the structure of the object. At this point, we can customize objects in the following way:

  • Literal notation
  • interface
  • Define a class or constructor
Literal notation
let a: {username: string; age: number} = { username: 'una'.age: 18 };

a.username; // ok 
a.age; // ok 
a.gender;// error 
Copy the code
  • Advantages: convenient, direct;
  • Disadvantages: Not conducive to reuse and maintenance;
interface
interface Person { username: string; age: number; };
let a: Person = { username: 'una'.age: 18 };

a.username; // ok 
a.age; // ok 
a.gender;// error 
Copy the code
  • Advantages: high reusability;
  • Disadvantages: The interface can only be used as a type annotation, not as a concrete value, it is only an abstract structure definition, not an entity, there is no specific function implementation;
Class and constructor
class Person { 
    constructor(public username: string.public age: number) {
    
    } 
}

a.username; // ok 
a.age; // ok 
a.gender;// error 
Copy the code
  • Advantages: Relatively powerful function, define the entity at the same time also define the corresponding type;
  • Disadvantages: complex, for example, only want to restrict the structure of the parameters received by a function, there is no need to specify a class, using interfaces is much simpler.
interface AjaxOptions { 
    url: string; 
    method: string; 
}

function ajax(options: AjaxOptions) { } 

ajax({url: ' '.method: 'get' });
Copy the code
Packaging object

We know that String is not the same as String, nor is it the same in TypeScript:

let a: string; // Simple type string
a = '1'; 
a = new String('1'); // Error String does not have a String (object does, base type does not have)

let b: String; // String object
b = new String('2');
b = '2'; // Ok is the opposite of the above
Copy the code

Object types are extensible, but simple types are not. String -> string loses data.

Function types

Functions are important in JavaScript, but also in TypeScript. Similarly, functions have their own type annotation format: function name (parameter 1: type, parameter 2: type…) : Return value type;

function add(x: number, y: number) :number { 
    return x + y; 
}
Copy the code

Type assertion: No more checking

Sometimes you’ll find that you know more about a value than TypeScript does.

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. TypeScript assumes that you have done the necessary checking.

Type assertions come in two forms:

  • Angle bracket syntax:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length; // Angle bracket syntax: 
      
       someValue
      
Copy the code
  • As grammar:
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length; // as syntax: someValue as string
Copy the code

The two forms are equivalent. Which to use is mostly a matter of personal preference; However, when you use JSX in TypeScript, only AS syntax assertions are allowed.

Three, detailed explanation of the interface

The interface definition

Interface: A way to annotate complex object types.

Multiple attributes in an interface can be separated by commas or semicolons.

The basic syntax definition structure of the interface is extremely simple:

interface Point { x: number; y: number; }
Copy the code

The code above defines a type that contains two attributes, x of type number and Y of type number. We can use this interface to type data:

let p1: Point = { x: 100.y: 100 };
p1.x = 200; // ok
p1.z = 100; // Error type 'Point' does not exist attribute 'z'
Copy the code

Note: An interface is a type and cannot be used as a value.

interface Point { x: number; y: number; }
let p1 = Point; // The error interface is a type and cannot be used as a value
Copy the code

The interface properties

Optional attribute

Interfaces can also define optional attributes, via? To annotate

interface Point { 
    x: number; 
    y: number; color? :string; / / optional attribute that is equal to the string | is undefined
}

let p1: Point = { x: 100.y: 100 }; // The color attribute is optional and does not report errors
Copy the code

Advantages:

  • Pre-define the possible attributes
  • You can catch errors when a property is referenced that does not exist
let p2: Point = { x: 100.y: 100.color: 'red' };
let p3: Point = { x: 100.y: 100.col: 'red' }; // Error type 'Point' does not have attribute 'col'
Copy the code

Read-only property

We can also mark a property as read-only by readonly, which cannot be reassigned except when initialized.

interface Point { 
    readonly x: number; // Read-only attribute
    readonly y: number; 
}

let p1: Point = { x: 100.y: 100 };
p1.x = 200; // The error read-only attribute cannot be reassigned except when initialized
Copy the code

ReadonlyArray<T>type

TypeScript also has the ReadonlyArray

type, which is similar to Array

except that all mutable methods are removed to ensure that arrays can never be modified after they are created:

let a: number[] = [1.2.3.4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // Index signatures in error type readonly number[] can only be read
ro.push(5); // Error 'readonly number[]' does not have property 'push'
ro.length = 100; // Error cannot be assigned to "length" because it is read-only
a = ro; // Error type "readonly number[]" is "readonly" and cannot be assigned to mutable type "number[]"
Copy the code

On the last line of the code above, you can see that even assigning the entire ReadonlyArray to a normal array does not work. But you can override it with type assertions:

a = ro as number[]; // ok
Copy the code

readonly vs const

The easiest way to determine whether to use readonly or const is to use it as a variable or as a property: const variable, property readonly.

Arbitrary attributes (additional attributes)

Sometimes we want to add arbitrary attributes to an interface, which can be done using index types.

TypeScript supports two types of index signatures: string and numeric.

Numeric index type:

interface Point { 
    x: number; 
    y: number; 
    [prop: number] :number; // Any attribute, numeric index
}
Copy the code

String index type:

interface Point { 
    x: number; 
    y: number; 
    [prop: string] :number; // Any attribute, string index
}
Copy the code

Note: The index signature parameter type must be either String or number, but both can occur at the same time.

interface Point { 
    [prop1: string] :string; 
    [prop2: number] :string; 
}
Copy the code

Numeric indexes are subtypes of string indexes.

Note: When both numeric and string indexes exist, the value type of the numeric index must be the value type or subtype of the string index.

This is because when you index a number, JavaScript converts it to a string and then indexes the object. That is, using 100 (number) to index is the same as using “100” (string) to index, so the two need to be consistent.

interface Point1 { 
    [prop1: string] :string;
    [prop2: number] :number; // error When both numeric and string indexes exist, the numeric value type must be a string value type or subtype
}

interface Point2 { 
    [prop1: string] :string;
    [prop2: number] :string; // ok
}

interface Point3 { 
    [prop1: string] :Object; 
    [prop2: number] :Date; // ok
}
Copy the code

Specific use can be seen in the following cases:

interface Point {
    readonly x: number;
    readonly y: number; color? :string; / / optional attribute string | is undefined
    // [key: string]: number; // Set 'color? : String 'returns an error.
    // The color attribute is obviously the same as the [key: string] setting, but the two types are different
    [key: string] :number | string |undefined; // Compatible Settings, extend the attributes to 3 types
}

let p1: Point = {
    x: 100.y: 100
}
// p1.x = 200; // error is a read-only property that can only be assigned at initialization

p1.z = 100; // Extend optional attributes [key: string]
p1[0] = 100; // The numeric index is a subtype of the string index, so no error is reported. But not the other way around
/ / is equivalent to
// p1['0'] = 100;
Copy the code

Finally, you can set the index signature to read-only, thus preventing index assignment:

interface ReadonlyStringArray {
    readonly [index: number] :string;
}

let myArray: ReadonlyStringArray = ["Alice"."Bob"];
myArray[2] = "Mallory"; // error is a read-only property that can only be assigned at initialization
Copy the code

Interface with

Merge multiple interfaces with the same name into one interface:

interface Box { 
    height: number; 
    width: number; 
}

interface Box { 
    scale: number; 
}

let box: Box = {height: 5.width: 6.scale: 10}
Copy the code
  • If the merged interface has non-function members of the same name, ensure that their types are the same; otherwise, an error will be reported.
  • Functions with the same name in the interface are overloaded (more on that later);

Use interfaces to describe functions

We can also use interfaces to describe a function

interface IFunc { 
    (a: string) :string; // Describe the function alone without a key
}

let fn: IFunc = function(a) {}
Copy the code

Note that if an interface is used to describe a function separately, there is no key.

interface IEventFunc {
    (e: MouseEvent): void
}

function on(el: HTMLElement, evname: string, callback: IEventFunc) {}let div = document.querySelector('div');
if (div) {
    on(div, 'click'.function(e) {
        e.clientX // HTMLElement has no clientX property, MouseEvent has clientX property
    });
}
Copy the code

Use interfaces to describe classes

Like C# or Java interfaces, TypeScript can use it to explicitly force a class to conform to a contract, using implements:

interface ClockInterface {
    currentTime: Date;
}

class Clock implements ClockInterface {
    currentTime: Date;
    constructor(h: number, m: number){}}Copy the code

Interfaces describe the public parts of a class, not the public and private parts. It doesn’t help you check if the class has some private members.

Specifically explained in the TS-Notes object-oriented programming module.

Advanced types

The joint type

A combined type can also be called a multiple-choice type, and we can choose the combined type annotation (or) when we want to annotate a variable as one of multiple types.

function css(ele: Element, attr: string, value: string | number) { 
    // ... 
}

let box = document.querySelector('.box'); 
// The document.querySelector method returns a union type

if (box) { // ts will indicate the possibility of null, plus more rigorous judgment
    css(box, 'width'.'100px'); 
    css(box, 'opacity'.1); 
    css(box, 'opacity'[1.2]); / / error
}
Copy the code

Cross type

A crossover type, also known as a merge type, can combine multiple types into a new type, extending an object (with) :

interface o1 {
    x: number.y: string
}; 

interface o2 {
    z: number
}; 

let o: o1 & o2 = Object.assign({}, {x: 1.y: '2'}, {z: 100});
console.log(o);
Copy the code

Tip:

TypeScript only converts syntax during compilation (such as extension operators, arrow functions, etc.), not apis (there is no need for conversion, but extension libraries are introduced to handle it). If we use an API that is not available in Target, Manual import is required, and bY default TypeScript loads the core type library based on target

** Target = es5: [“dom”, “es5”, “scripthost”] **

[“dom”, “es6”, “dom. Iterable “, “scripthost”]

If you use code that is not loaded by default, you can set it through the lib option

www.typescriptlang.org/docs/handbo…

Literal type

Sometimes, when we want to annotate not a type but a fixed value, we can use literal types, which are more useful in conjunction with union types

function setPosition(ele: Element, direction: 'left' | 'top' | 'right' | 'bottom') {
    // ... 
}

box && setDirection(box, 'bottom'); // ok 
box && setDirection(box, 'hehe'); // error 
Copy the code

Type the alias

When type annotations are complicated, we can give them a relatively simple name, which we call a type alias.

type dir = 'left' | 'top' | 'right' | 'bottom'; 
function setPosition(ele: Element, direction: dir) { 
    // ... 
}
Copy the code

Use type aliases to define function types

It is important to note that if you use type to define function types, it is a little different from the interface

// Use type aliases to define function types
type callback = (a: string) = > string; 
let fn: callback = function(a) {}; 

// Or directly
let fn: (a: string) = > string = function(a) {}
Copy the code

Difference between interface and Type

  • interface
    • Can only be describedobject / class / functionThe type of
    • The interface with the same name is automatically merged to facilitate expansion
  • type
    • Can describe all data
    • Can’t wish

Type inference

While explicitly annotating types every time is cumbersome, TypeScript provides a more convenient feature: type derivation. The TypeScript compiler automatically deduces type annotations based on the current context. This happens when:

  • Initialize a variable;
  • Set the default parameter values of the function.
  • Return the function value;
let x = 1; // Automatically infer that x is number
x = 'a'; // Cannot assign type "a" to type "number"

// Function parameter types and function return values are automatically inferred based on the corresponding default values and return values
Function fn(a? : number): number
function fn(a = 1) { return a * a }
Copy the code

Types of assertions

Sometimes, we may annotate a more precise type (narrowing the type annotation), for example:

let img = document.querySelector('#img');
Copy the code

We can see that the img type is Element, but Element is a generic Element type. If we access SRC, we need to type it more precisely: The HTMLImageElement type, at which point we can use type assertion, which is similar to a type conversion:

let img = <HTMLImageElement>document.querySelector('#img');
/ / or
let img = document.querySelector('#img') as HTMLImageElement;
Copy the code

Note: An assertion is a prediction and does not have an actual effect on the data itself, i.e., it is like a conversion, but it is not actually converted.

Five, function details

The annotation of a function contains:

  • parameter
  • Return value (If the function does not return any value, you must also specify the return value type as void instead of empty)

For return values, we use the => symbol before the function and return value type to make it clear.

// 1
function fn1(a: string) :string {
    return ' ';
}

// 2. type Type alias annotation function
let fn2: (a: string) = > string = function(a: string) {
    return ' ';
}

// The TypeScript compiler automatically recognizes a type that is specified on one side of an assignment statement but not on the other
let fn3: (a: string) = > string = function(a) {
    return ' ';
}

// 3. Type alias annotation function, split type variables
type callback = (a: string) = > string;

// The type alias uses an argument named a and the function uses an argument named b
// As long as the parameter type matches, it is considered a valid function type, regardless of whether the parameter name is correct
let fn4: callback = function(b) {
    return ' '
}

// 4. Interface Annotation function
interface ICallBack {
    (a: string) :string; // Use interfaces to describe functions separately, without keys
}

let fn5: ICallBack = function(c) {
    return ' ';
}
Copy the code

Function parameters

Optional parameters

Add? After the parameter name. To note that this parameter is optional. Optional arguments must be followed by required arguments.

let div = document.querySelector('div'); 
function css(el: HTMLElement, attr: string, val? :any) {}
// Error:
// function css(el? : HTMLElement, attr: string, val: any) {}

div && css( div, 'width'.'100px' ); / / set
div && css( div, 'width' ); / / to get
Copy the code

The default parameters

We can also set the parameters to default values:

  • Parameters with default values are also optional
  • Parameters with default values can automatically derive types from their values

Parameters with default values do not need to come after required parameters. If a parameter with a default value appears before a mandatory parameter, the user must explicitly pass undefined to get the default value.

function sort1(items: Array<number>, order = 'desc') {} 

sort1([1.2.3]); 

// You can also limit the value by combining types and set the default value
/ / 'desc' | 'asc' joint types, the limit value
// = 'desc' sets the default value
function sort2(items: Array<number>, order: 'desc'|'asc' = 'desc') {} 

sort2([1.2.3]); // ok 
sort2([1.2.3].'asc'); // ok 
sort2([1.2.3].'abc'); // error 

function sort3(order: 'desc'|'asc' = 'desc', items: Array<number>) {} 

sort3([1.2.3]); // error
sort3(undefined[1.2.3]); // ok
sort3('asc'[1.2.3]); // ok
Copy the code

The remaining parameters

The remaining parameters are an array, so be careful when annotating

interface IObj { 
    [key:string] :any; 
}

function merge(target: IObj, ... others:Array<IObj>) { 
    return others.reduce( (prev, currnet) = > { 
        prev = Object.assign(prev, currnet); // Merge parameters
        return prev; 
    }, target ); 
}

// target => {x: 1}, others => {y: 2}, {z: 3}
let newObj = merge({x: 1}, {y: 2}, {z: 3});
Copy the code

This in the function

Common function

For normal functions, this changes with the calling environment, so by default, this is labeled any, but we can explicitly label the type of this on the first argument bit of the function (which does not take up the actual argument position).

interface T { 
    a: number; 
    fn: (x: number) = > void; 
}

let obj1: T = { 
    a: 1.fn(x: number) { 
        // This is of type any
        console.log(this); 
        // this.d // any
        (<T>this).d // Type assertion}}let obj2: T = { 
    a: 1.fn(this: T, x: number) { 
        // The type of this is indicated by the first argument bit, which has no effect on the actual argument
        console.log(this); // There is a type hint
    } 
}
obj2.fn(1);
Copy the code

Arrow function

The arrow function’s this cannot be annotated like a normal function; its this annotation type depends on the annotation type of this in its scope.

interface T { 
    a: number; 
    fn: (x: number) = > void; 
}

let obj2: T = { 
    a: 2.fn(this: T) { The arrow function's this is fixed, depending on the annotation type of this in its scope
        return () = > { 
            // T 
            console.log(this); }}}Copy the code

Function overloading

Sometimes, the same function will take different types of arguments and return different types of values. We can use function overloading to do this.

function showOrHide(ele: HTMLElement, attr: string, value: 'block' | 'none' | number) {}

let div = document.querySelector('div'); 
if (div) { 
    showOrHide( div, 'display'.'none' ); 
    showOrHide( div, 'opacity'.1 ); 
    
    // Error, although passed the annotation, but there is clearly a problem
    // Although it is possible to accept different types of parameters simultaneously through the union type, multiple parameters are a combination of patterns, and we need a corresponding relationship
    showOrHide( div, 'display'.1 ); 
}
Copy the code

Function overloading:

  • Function overloading is merging rather than overwriting
  • Function overloading allows you to set the mapping between different parameters
  • Overloaded function types only need to define structures, not entities, like interfaces
  • Find the list of overloads and try to use the first overload definition, if it matches.
// Function overloading allows you to set different parameter mappings
// Overloaded function types only need to define structures, not entities, like interfaces
function showOrHide(ele: HTMLElement, attr: 'display', value: 'block' | 'none'); 
function showOrHide(ele: HTMLElement, attr: 'opacity', value: number); 
// Note that function showOrHide(ele: HTMLElement, attr: string, value: any) is not part of the overloaded list, but rather the concrete function implementation.
// There are only two overloads. Calling showOrHide with other arguments produces an error.
function showOrHide(ele: HTMLElement, attr: string, value: any) { 
    ele.style[attr] = value; 
}

let div = document.querySelector('div'); 
if (div) { 
    showOrHide( div, 'display'.'none' ); 
    showOrHide( div, 'opacity'.1 ); 
    showOrHide( div, 'display'.1 );  // error
}
Copy the code
interface PlainObject { 
    [key: string] :string | number; 
}

function css(ele: HTMLElement, attr: PlainObject); / / object
function css(ele: HTMLElement, attr: string, value: string | number); / / string
function css(ele: HTMLElement, attr: any, value? :any) { 
    if (typeof attr === 'string' && value) { 
        ele.style[attr] = value; 
    }
    if (typeof attr === 'object') { 
        for (let key inattr) { ele.style[attr] = attr[key]; }}}let div = document.querySelector('div'); 
if (div) { 
    css(div, 'width'.'100px'); 
    css(div, { width: '100px'});
    css(div, 'width'); // value? If you don't use overloading, there's a problem here
}
Copy the code

In order for the compiler to choose the right type of check, it is similar to the processing flow in JavaScript. It looks up the overload list, tries to use the first overload definition, and uses this one if it matches. Therefore, when defining overloads, always put the most precise definition first.