An inside volume note about TypeScript

Author: Attention 🐴

Github: Github

Nuggets: Go in and have a look 🐴

Hobbies: Americano More Ice!

An inside volume note about Typescript

brief introduction

TypeScript is a superset of JavaScript types, which can be compiled into pure JavaScript. The compiled JavaScript can run on any browser. TypeScript compiler can run on any server and any system. TypeScript is open source.

install

npm install -g typescript

After installation, there will be a tsc global command

Write the first demo

// hello.ts
function sayHello(person: string) {
    return 'Hello, ' + person;
}

let user = 'Tom';
console.log(sayHello(user));

Execute TSC hello TS, generate hello js

function sayHello(person) {
    return 'Hello, ' + person;
}
var user = 'Tom';
console.log(sayHello(user));

Compare the differences between the two:

  • The parameters inserted in ts will have parameter types. ts will only perform static check. If an error is found, an error will be reported during compilation
  • let is the keyword of ES6, similar to var, which is used to define a local variable

Basics

Raw data type

There are two types of JavaScript: Primitive data types and Object types.

Raw data types include Boolean, numeric, string, null, undefined, and the new type Symbol in ES6.

Boolean value

// Boolean is the basic type in JavaScript, and Boolean is the constructor in JavaScript
let isDone: boolean = false;
// A Boolean object is returned
let createdByNewBoolean: Boolean = new Boolean(1);
// Returns a boolean type
let createdByBoolean: boolean = Boolean(1);

numerical value

// Where 0b1010 and 0o744 are binary and octal representations in ES6, which will be compiled into decimal numbers
let decLiteral: number = 6;
let hexLiternal: number = 0xf00d;
// Binary representation in ES6
let binaryLiteral: number = 0b1010;
// Octal notation in ES6
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;

character string

// `Used to define Template Strings, ${expr} is used to embed expressions
let myName: string = 'Tom';
let myAge: number = 25;
let sentence: string = `Hello, my name is ${myName}.I'll be ${myAge + 1} years old next day`;

Null value

// Indicates that there is no return value
function alertName(): void {
    alert('My name is Tom');
}
// It's no use declaring a variable of void type, because you can only assign it to undefined and null
let unusable: void = undefined;
// Null and Undefined
let u: undefined = undefined;
let n: null = null;

// The difference from void is that undefined and null are subtypes of all types
// Variables of type undefined can be assigned to variables of type number
let num: number = undefined;
// perhaps
let u: undefined;
let num: number = u;

// And void can't
let u: void;
let num: number = u;
// Type 'void' is not assignable to type 'number'.

Arbitrary value

Arbitrary value (Any) is used to indicate that assignment to Any type is allowed

After declaring a variable as an arbitrary value, the type of content returned by any operation on it is an arbitrary value

// usage
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
// Access any properties
let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);
// Call any method
let anyThing: any = 'Tom';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');

If a variable is declared without specifying its type, it will be recognized as any value type

// let something: any;
let something;
something = 'seven';
something = 7;
something.setName('Tom');

type inference

TypeScript will infer a type when there is no explicit specified type, which is type inference

// If no type is specified, but the assignment is string, it is considered as string type
// Compilation error
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

// If there is no assignment at the time of definition, it will be inferred as any type regardless of whether there is assignment later
// Not type checked at all
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

Union type

Union Types means that the value can be one of multiple types

// Union types use | to separate each type
// Correct. myFavoriteNumber is allowed to be of type string or number, but not of other types
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
// report errors
let myFavoriteNumber: string | number;
myFavoriteNumber = true;

When TypeScript does not know which type the variable of a union type is, we can only access the properties or methods common to all types of the union type

// length is not a common attribute of string and number, so an error will be reported
function getLength(something: string | number): number {
    return something.length;
}
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'.

There is no problem accessing the common properties of string and number

function getString(something: string | number): string {
    return something.toString();
}

When a variable of joint type is assigned a value, it will infer a type according to the rules of type inference

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
// Inferred as string
console.log(myFavoriteNumber.length); // 5
// Inferred as number
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // Compile error

Object type - Interface

In TypeScript, we use Interfaces to define the type of objects.

  • What is an interface
    In object-oriented language, Interfaces is a very important concept. It is the abstraction of behavior, and how to act needs to be implement ed by classes.

The interface in TypeScript is a very flexible concept. In addition to abstracting part of the behavior of a class, it is also commonly used to describe the "Shape of an object".

interface Person {
    name: string;
    age: number;
}
// tom, whose type is Person, constrains that the shape of tom must be consistent with the interface Person
// That is, the number and type of attributes should be consistent
let tom: Person = {
    name: 'Tom',
    age: 25
};
  • optional attribute
    If you do not want to match a shape exactly, you can use the following optional attributes:
interface Person {
    name: string;
    age?: number;
}
// Optional attribute age, free or nonexistent
let tom: Person = {
    name: 'Tom'
};
  • Arbitrary attribute
    An interface is allowed to have any attributes. The following methods can be used
interface Person {
    name: string;
    age?: number;
    [propName: string]: any;
}
let tom: Person = {
    name: 'Tom',
    gender: 'male'
};

Note: once any attribute is defined, the type of the determined attribute and the optional attribute must be a subset of its type:

interface Person {
    name: string;
    age?: number;
    [propName: string]: string;
}
// An error is reported. age is not a string
let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

// Use union type in any attribute to solve the above problem
interface Person {
    name: string;
    age?: number;
    [propName: string]: string | number;
}
let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};
  • Read only attribute

Sometimes we want some fields in the object to be assigned only when it is created, so we can use readonly to define the read-only attribute

interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}
let tom: Person = {
    id: 89757,
    name: 'Tom',
    gender: 'male'
};
// An error is reported because the property is read-only
tom.id = 9527;

Note that the read-only constraint exists when the object is assigned a value for the first time, not when the read-only attribute is assigned a value for the first time

interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}
// The first error is reported and the id is not assigned
let tom: Person = {
    name: 'Tom',
    gender: 'male'
};
// The second error is reported because it is read-only
tom.id = 89757;

Array type

In TypeScript, array types can be defined in many ways

For example:

let fibonacci: number[] = [1, 1, 2, 3, 5];

No other types are allowed in the items of the array

let fibonacci: number[] = [1, '1', 2, 3, 5];
// Type 'string' is not assignable to type 'number'.

The parameters of some methods of the array will also be limited according to the types agreed upon when the array is defined

let fibonacci: number[] = [1, 1, 2, 3, 5];
fibonacci.push('8');
// Argument of type '"8"' is not assignable to parameter of type 'number'.
  • Array generic

Array < ElemType > to represent the array

let fibonacci: Array<number> = [1, 1, 2, 3, 5];
  • Interface representation array
// NumberArray means that as long as the type of index is number, the type of value must be number
interface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
  • Array like object

Array like objects are not array types, such as arguments

function sum() {
    let args: number[] = arguments;
}
// It can't be described by ordinary array, but by interface
// Value must be of type other than number
// It also has two attributes: length and callee
function sum() {
    let args: {
        [index: number]: number;
        length: number;
        callee: Function;
    } = arguments;
}

In fact, commonly used class arrays have their own interface definitions, such as iargements, NodeList, htmlcollection, etc

function sum() {
    let args: IArguments = arguments;
}
// Iargements are types defined in TypeScript
interface IArguments {
    [index: number]: any;
    length: number;
    callee: Function;
}
  • Application of any in array

A common practice is to use any to indicate that any type is allowed in the array

let list: any[] = ['xiaobiao', 25, { website: 'http://xcatliu.com' }];

Function type

In JavaScript, there are two common ways to define functions

  • Function Declaration
  • Function Expression
// Function Declaration
function sum(x, y) {
    return x + y;
}

// Function Expression
let mySum = function (x, y) {
    return x + y;
};
  • Function declaration

ts is slightly different in that it uses constraints

function sum(x: number, y: number): number {
    return x + y;
}
// It is not allowed to enter redundant (or less than required) parameters
// report errors
sum(1, 2, 3);
sum(1);
// correct
sum(1,2)
  • Function expression

In the type definition of TypeScript, = > is used to represent the definition of function. On the left is the input type, which needs to be enclosed in parentheses, and on the right is the output type.

In ES6, = > is called arrow function, which is widely used

let mySum = function (x: number, y: number): number {
    return x + y;
};
// The above code only defines the type of anonymous functions on the right side of the equal sign, while mySum on the left side of the equal sign is inferred by type inference through assignment operation
// If you manually add a type to mySum, this should be the case
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};
  • Defining the shape of a function with an interface
interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    return source.search(subString) !== -1;
}
  • Optional parameters
function buildName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

Required parameters are not allowed after optional parameters

function buildName(firstName?: string, lastName: string) {
    if (firstName) {
        return firstName + ' ' + lastName;
    } else {
        return lastName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName(undefined, 'Tom');

// index.ts(1,40): error TS1016: A required parameter cannot follow an optional parameter.
  • Parameter defaults

TypeScript recognizes parameters with default values as optional:

function buildName(firstName: string, lastName: string = 'Cat') {
    return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');

Not limited by "optional parameters must be followed by required parameters":

function buildName(firstName: string = 'Tom', lastName: string) {
    return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');
  • Residual parameters
function push(array: any[], ...items: any[]) {
    items.forEach(function(item) {
        array.push(item);
    });
}
let a = [];
push(a, 1, 2, 3);
  • heavy load

Overloading allows a function to do different things when it accepts different numbers or types of parameters.

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

Type Assertion

Type assertions can be used to manually specify the type of a value

Syntax:

Value as type or < type > value, the former is recommended

Type assertion purpose

  • Assert a union type as one of the types
// The reason is (animal as Fish) The code of swim () hides the possibility that animal may be Cat and directly asserts animal as Fish
// The TypeScript compiler trusted our assertion, so there were no compilation errors when calling swim()
interface Cat {
    name: string;
    run(): void;
}
interface Fish {
    name: string;
    swim(): void;
}
function swim(animal: Cat | Fish) {
    (animal as Fish).swim();
}
const tom: Cat = {
    name: 'Tom',
    run() { console.log('run') }
};
swim(tom);
// Uncaught TypeError: animal.swim is not a function`
  • Assert a parent class as a more specific subclass

interface type can only use type assertion to judge whether code is AirError
class type can be instanceof

class ApiError extends Error {
    code: number = 0;
}
class HttpError extends Error {
    statusCode: number = 200;
}
function isApiError(error: Error) {
    if (typeof (error as ApiError).code === 'number') {
    // Here is a more appropriate approach
    // Indeed, instanceof is more appropriate, because ApiError is a JavaScript class, which can judge whether error is its instance through instanceof
    // if (error instanceof ApiError) {
        return true;
    }
    return false;
}
  • Assert any type as any

It's very likely to cover up real type errors, so don't use as any if you're not very sure

Syntax: (window as any) foo = 1;

  • Assert any as a concrete type

It is better to assert the return value after calling it into a precise type, which facilitates subsequent operations (improve maintainability)

function getCacheData(key: string): any {
    return (window as any).cache[key];
}
interface Cat {
    name: string;
    run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();

Restriction on the type of assertion

  • A union type can be asserted as one of these types
  • A parent class can be asserted as a child class
  • Any type can be asserted as any
  • Any can be asserted as any type
  • To enable A to be asserted as B, only A is compatible with B or B is compatible with A

When Animal is Cat compatible, they can type assert each other

interface Animal {
    name: string;
}
interface Cat {
    name: string;
    run(): void;
}
function testAnimal(animal: Animal) {
    return (animal as Cat);
}
function testCat(cat: Cat) {
    return (cat as Animal);
}

Double assertion

Never use double assertions unless you have to

Type assertion vs type conversion

Type assertion is not a type conversion, it does not really affect the type of variable

To perform type conversion, you need to call the method of type conversion directly

function toBoolean(something: any): boolean {
    return Boolean(something);
}
toBoolean(1);
// The return value is true

Type assertion vs type declaration

interface Animal {
    name: string;
}
interface Cat {
    name: string;
    run(): void;
}
const animal: Animal = {
    name: 'tom'
};
// Since animal is Cat compatible, it is possible to assign an animal assertion as Cat to tom
let tom = animal as Cat;
// let tom: Cat = animal;  report errors
// Animal can be regarded as the parent class of Cat. Of course, the instance of the parent class cannot be assigned to a variable of type subclass.

If Animal is asserted as Cat, it only needs to meet the requirements of Animal compatible Cat or Cat compatible Animal.
The assignment of "Animal" to "tom" needs to be Cat compatible with "Animal"

To sum up, type declarations are more rigorous and elegant than type assertions

Type assertion vs generics

Before modification

function getCacheData(key: string): any {
    return (window as any).cache[key];
}
interface Cat {
    name: string;
    run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();

After modification

function getCacheData<T>(key: string): T {
    return (window as any).cache[key];
}
interface Cat {
    name: string;
    run(): void;
}
const tom = getCacheData<Cat>('tom');
tom.run();

By adding a generic < T > to the getCacheData function, we can more standardize the constraint on the return value of getCacheData, which also removes any in the code. It is an optimal solution.

Declaration document

When using a third-party library, we need to reference its declaration file to obtain the corresponding code completion, interface prompt and other functions

New syntax index

`declare var` Declare global variables
`declare function` Declare global methods
`declare class` Declare global class
`declare enum` Declare global enumeration type
`declare namespace` Declare a global object (with child attributes)
`interface` and type Declare global type
`export` Export variables
`export namespace` Export (objects with child attributes)
`export default` ES6 Default export
`export =` commonjs Export module
`export as namespace` UMD Library declaration global variables
`declare global` Extended global variable
`declare module` Expansion module
`///< reference / > ` three slash instruction

What is a declaration statement

If we want to use a third-party library jQuery, a common way is to introduce jQuery through the script tag in html, and then we can use the global variable $or jQuery.

// declare var does not really define a variable, but defines the type of global variable jQuery
// It will only be used for compile time checking and will be deleted in the compilation results
declare var jQuery: (selector: string) => any;
jQuery('#foo');

After compilation: jQuery('#foo');

What is a declaration file

Usually we put the declaration statements in a separate file (jQuery.d.ts), which is the declaration file

// src/jQuery.d.ts
declare var jQuery: (selector: string) => any;

// src/index.ts
jQuery('#foo');

The declaration file must be in d.ts is suffix

Generally speaking, ts will parse all *. In the project ts file, of course, also contains d. File at the end of ts. So when we put jQuery d. When ts is placed in the project, all other * The type definition of jQuery can be obtained from the ts file

/path/to/project
├── src
|  ├── index.ts
|  └── jQuery.d.ts
└── tsconfig.json

If it still cannot be parsed, you can check tsconfig Configure files, include, and exclude in JSON to ensure that it contains jQuery d. TS file

Third party declaration document

It is recommended to use @ types to uniformly manage the declaration files of third-party libraries.

@The use of types is very simple. You can directly install the corresponding declaration module with npm, such as

npm install @types/jquery --save-dev

Writing declaration documents

global variable

When using the declaration file of global variables, if it is installed as NPM install @ types / xxx -- save dev, no configuration is required. If the declaration file is stored directly in the current project, it is recommended that (jQuery.d.ts) and other source code be placed in the src directory (or the corresponding source code directory)

The declaration files of global variables mainly have the following syntax:

`declare var` Declare global variables
`declare function` Declare global methods
`declare class` Declare global class
`declare enum` Declare global enumeration type
`declare namespace` Declare a global object (with child attributes)
`interface and type` Declare global type
declare var

Generally speaking, global variables are constants that cannot be modified, so const should be used instead of var or let in most cases.

// src/jQuery.d.ts

declare const jQuery: (selector: string) => any;

// jQuery('#foo');
// Using the jQuery type defined by declare const, it is forbidden to modify this global variable
// ERROR: Cannot assign to 'jQuery' because it is a constant or a read-only property.

jQuery = function(selector) {
    return document.querySelector(selector);
};

// Note: only types can be defined in the declaration statement. Do not define specific implementations in the declaration statement
// declare const jQuery = function(selector) {
//     return document.querySelector(selector);
// };
// ERROR: An implementation cannot be declared in ambient contexts.
declare function

The type used to define the global function. jQuery is actually a function, so it can also be defined by function

example:

// src/jQuery.d.ts
declare function jQuery(selector: string): any;

// src/index.ts
jQuery('#foo');

Function overloading is also supported in the declaration statement of function type

// src/jQuery.d.ts
declare function jQuery(selector: string): any;
declare function jQuery(domReadyCallback: () => any): any;

// src/index.ts
jQuery('#foo');
jQuery(function() {
    alert('Dom Ready!');
});
declare class

When a global variable is a class, we use declare class to define its type

example:

// src/Animal.d.ts
declare class Animal {
    name: string;
    constructor(name: string);
    sayHi(): string;
}

// src/index.ts
let cat = new Animal('Tom');

The declare class statement can only be used to define the type (and so can declare var). It cannot be used to define the specific implementation. For example, if you define the specific implementation of sayHi method, an error will be reported

Error example:

// src/Animal.d.ts
declare class Animal {
    name: string;
    constructor(name: string);
    sayHi() {
        return `My name is ${this.name}`;
    };
    // ERROR: An implementation cannot be declared in ambient contexts.
}
declare enum

Enumeration types defined with declare enum are also called Ambient Enums

// src/Directions.d.ts
declare enum Directions {
    Up,
    Down,
    Left,
    Right
}

// src/index.ts
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]
declare namespace

Namespace is a keyword created by ts to solve modularization in the early days. It is called namespace in Chinese

JQuery is a global variable. It is an object that provides a jQuery Ajax methods can be called, so we should use declare namespace jQuery to declare this global variable with multiple sub attributes

Within declare namespace, we directly use function ajax to declare functions instead of declare function ajax. Similarly, const, class, enum and other statements can be used

// src/jQuery.d.ts
declare namespace jQuery {
    function ajax(url: string, settings?: any): void;
    const version: number;
    class Event {
        blur(eventType: EventType): void
    }
    enum EventType {
        CustomClick
    }
}

// src/index.ts
jQuery.ajax('/api/get_something');
console.log(jQuery.version);
const e = new jQuery.Event();
e.blur(jQuery.EventType.CustomClick);
interface and type

In the type declaration file, we can directly use interface or type to declare a global interface or type

// src/jQuery.d.ts
interface AjaxSettings {
    method?: 'GET' | 'POST'
    data?: any;
}
declare namespace jQuery {
    function ajax(url: string, settings?: AjaxSettings): void;
}

// src/index.ts
let settings: AjaxSettings = {
    method: 'POST',
    data: {
        name: 'foo'
    }
};
jQuery.ajax('/api/post_something', settings);
Prevent naming conflicts

The interface or type exposed in the outermost layer will act as a global type in the whole project. We should reduce the number of global variables or global types as much as possible. So it's best to put them under namespace

Declaration merge
// src/jQuery.d.ts
declare function jQuery(selector: string): any;
declare namespace jQuery {
    function ajax(url: string, settings?: any): void;
}

// src/index.ts
jQuery('#foo');
jQuery.ajax('/api/get_something');
npm package

The declaration file of npm package mainly has the following syntax:

export Export variables
export namespace Export (objects with child attributes)
export default ES6 Default export
export = commonjs Export module
export

The declaration file of npm package is very different from that of global variables. In the declaration file of npm package, declare will no longer declare a global variable, but only declare a local variable in the current file. These type declarations are not applied until export is used in the declaration file and then imported by the user import

// types/foo/index.d.ts
export const name: string;
export function getName(): string;
export class Animal {
    constructor(name: string);
    sayHi(): string;
}
export enum Directions {
    Up,
    Down,
    Left,
    Right
}
export interface Options {
    data: any;
}

The corresponding import and use modules should be as follows:

import { name, getName, Animal, Directions, Options } from 'foo';

console.log(name);
let myName = getName();
let cat = new Animal('Tom');
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
let options: Options = {
    data: {
        name: 'foo'
    }
};
Mix declare and export

declare multiple variables first, and then export them at one time

// types/foo/index.d.ts

declare const name: string;
declare function getName(): string;
declare class Animal {
    constructor(name: string);
    sayHi(): string;
}
declare enum Directions {
    Up,
    Down,
    Left,
    Right
}
interface Options {
    data: any;
}

export { name, getName, Animal, Directions, Options };

Note: declare is not required before the interface

export namespace

export namespace is used to export an object with sub attributes

// types/foo/index.d.ts

export namespace foo {
    const name: string;
    namespace bar {
        function baz(): string;
    }
}
// src/index.ts

import { foo } from 'foo';

console.log(foo.name);
foo.bar.baz();
export default

In the ES6 module system, export default can be used to export a default value. The user can import this default value with import foo from 'foo' instead of import {foo} from 'foo'

Only function, class and interface can be exported directly by default. Other variables need to be defined first and then exported by default

// types/foo/index.d.ts

export default function foo(): string;
// src/index.ts

import foo from 'foo';

foo();

For other default exports, the export statement is usually placed at the front of the whole declaration file

export default Directions;

declare enum Directions {
    Up,
    Down,
    Left,
    Right
}
export =

In the commonjs specification, we export a module in the following way

// Overall export
module.exports = foo;
// Single export
exports.bar = bar;

In ts, there are many ways to export this module. The first way is const= require

// Overall import
const foo = require('foo');
// Single import
const bar = require('foo').bar;

The second way is to import From. Note that for overall export, you need to use import * as to import

// Overall import
import * as foo from 'foo';
// Single import
import { bar } from 'foo';

The third way is to import Require, which is also the official recommended method of ts

// Overall import
import foo = require('foo');
// Single import
import bar = foo.bar;

For this library using the commonjs specification, if you want to write a type declaration file for it, you need to use the syntax of export = for it

// types/foo/index.d.ts

export = foo;

declare function foo(): string;
declare namespace foo {
    const bar: number;
}

UMD Library

export as namespace

Generally, when using export as namespace (function: to declare additional global variables), you first have the declaration file of npm package, and then add an export as namespace statement based on it to declare a declared variable as a global variable, for example:

// types/foo/index.d.ts

export as namespace foo;
// export default foo;
export = foo;

declare function foo(): string;
declare namespace foo {
    const bar: number;
}

Direct extension of global variables

Some third-party libraries extend a global variable, but the type of the global variable is not updated accordingly, which will lead to ts compilation errors. At this time, it is necessary to extend the type of the global variable. For example, extend the String type

interface String {
    prependHello(): string;
}

'foo'.prependHello();

You can also use declare namespace to add type declarations to existing namespaces

// types/jquery-plugin/index.d.ts

declare namespace JQuery {
    interface CustomOptions {
        bar: string;
    }
}

interface JQueryStatic {
    foo(options: JQuery.CustomOptions): string;
}
// src/index.ts

jQuery.foo({
    bar: ''
});

Expand global variables in npm package or UMD Library

declare global

The type of global variable can be extended in the declaration file of npm package or UMD library

// types/foo/index.d.ts

declare global {
    interface String {
        prependHello(): string;
    }
}

// Note that even if this declaration file does not need to export anything, it still needs to export an empty object to tell the compiler that this is a module declaration file
// Instead of a declaration file for a global variable
export {};
// src/index.ts

'bar'.prependHello();

Module plug-in

ts provides a syntax declare module, which can be used to extend the type of the original module

// types/moment-plugin/index.d.ts

import * as moment from 'moment';

declare module 'moment' {
    export function foo(): moment.CalendarKey;
}
// src/index.ts

import * as moment from 'moment';
import 'moment-plugin';

moment.foo();

You can also declare multiple module types in a single file

// types/foo-bar.d.ts

declare module 'foo' {
    export interface Foo {
        foo: string;
    }
}

declare module 'bar' {
    export function bar(): string;
}
// src/index.ts

import { Foo } from 'foo';
import * as bar from 'bar';

let f: Foo;
bar.bar();

Dependency in declaration file

A declaration file sometimes depends on the type in another declaration file. For example, in the previous example of declare module, we imported moment in the declaration file and used moment Calendarkey this type

// types/moment-plugin/index.d.ts

import * as moment from 'moment';

declare module 'moment' {
    export function foo(): moment.CalendarKey;
}
Triple slash instruction

Similar to import in the declaration file, it can be used to import another declaration file. The difference from import is that we need to use the triple slash instruction instead of import only in the following scenarios:

  • When we are writing a declaration file for a global variable
  • When we need to rely on the declaration file of a global variable
Write a declaration file for a global variable

These scenarios sound awkward, but they are actually easy to understand - the import and export keywords are not allowed in the declaration file of global variables. Once it appears, it will be regarded as an npm package or UMD library, which is no longer the declaration file of global variables. Therefore, when we write the declaration file of a global variable, if we need to refer to the type of another library, we must use the triple slash instruction

// types/jquery-plugin/index.d.ts

/// <reference types="jquery" />

declare function foo(options: JQuery.AjaxSettings): string;
// src/index.ts

foo({});

The syntax of the triple slash instruction is as above, and the xml format is used after / / / to add the dependency on the jquery type, so that jquery can be used in the declaration file Ajax settings type

Note that the triple slash instruction must be placed at the top of the file, and only one or more lines of comments are allowed in front of the triple slash instruction

A declaration file that depends on a global variable

In another scenario, when we need to rely on the declaration file of a global variable, because the global variable does not support import, of course, we must use the triple slash instruction to import

// types/node-plugin/index.d.ts

/// <reference types="node" />

export function foo(p: NodeJS.Process): string;
// types/node-plugin/index.d.ts

/// <reference types="node" />

export function foo(p: NodeJS.Process): string;
Split declaration file

When the declaration file of our global variable is too large, we can improve the maintainability of the code by splitting it into multiple files and introducing them one by one in an entry file. For example, jQuery's declaration file is like this

// node_modules/@types/jquery/index.d.ts

/// <reference types="sizzle" />
/// <reference path="JQueryStatic.d.ts" />
/// <reference path="JQuery.d.ts" />
/// <reference path="misc.d.ts" />
/// <reference path="legacy.d.ts" />

export = jQuery;

Automatic generation of declaration files

Method 1: syntax TSC XX ts -d or tsc xx. ts --declaration

The difference is that types is used to declare a dependency on another library, while path is used to declare a dependency on another file

Method 2: in tsconfig Add the declaration option to JSON. Here, use tsconfig JSON as an example

{
    "compilerOptions": {
        "module": "commonjs",
        "outDir": "lib",
        "declaration": true
    }
}

Release statement document

After we have written the declaration file for a library, the next step is to publish it.

There are two options:

  • Put the declaration file and source code together
  • Publish the declaration file to @ types

Built in object

There are many built-in objects in JavaScript, which can be directly used as defined types in TypeScript.

Built in objects are objects that exist on the Global scope according to the standard. The standard here refers to the standard of ECMAScript and other environments (such as DOM)

Built in object of ECMAScript

The built-in objects provided by ECMAScript standard include:
Boolean, Error, Date, RegExp, etc.

let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;

More built-in objects
Core library definition

DOM and BOM built-in objects

Built in objects provided by DOM and BOM include:
Document, HTMLElement, Event, NodeList, etc.

// Commonly used
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
  // Do something
});

Core library definition

Definition file of TypeScript core library

The definition file of TypeScript core library defines the types required by all browser environments, and is preset in TypeScript.

Write node with TypeScript js

Node.js is not part of the built-in object. If you want to write node with TypeScript JS, you need to import a third-party declaration file:
npm install @types/node --save-dev

Advanced

Type alias

Commonly used for union types

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

String literal type

String literal type is used to restrict that the value can only be one of several strings.

type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
    // do something
}
handleEvent(document.getElementById('hello'), 'scroll');  // no problem
handleEvent(document.getElementById('world'), 'dblclick'); // An error is reported. event cannot be 'dblclick'
// index.ts(7,47): error TS2345: Argument of type '"dblclick"' is not assignable to parameter of type 'EventNames'.

Note that both type aliases and string literal types are defined using type.

tuple

Arrays combine objects of the same type, while tuples combine objects of different types.

Tuples originate from functional programming languages (such as F#), in which tuples are frequently used

let tom: [string, number];
tom[0] = 'Tom';
tom[1] = 25;
tom[0].slice(1);
tom[1].toFixed(2);

Out of bounds array: when an out of bounds element is added, its type is limited to the union type of each type in the tuple

let tom: [string, number];
tom = ['Tom', 25];
tom.push('male');
tom.push(true);
// Argument of type 'true' is not assignable to parameter of type 'string | number'.

enumeration

Enum type is used for scenes whose values are limited to a certain range. For example, there can only be seven days a week, and the color is limited to red, green and blue

Enum members are assigned a number that increases from 0, and the enum value is inversely mapped to the enum name

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};

console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true

Manual assignment

TypeScript will not notice if the enumeration item that is not manually assigned repeats the enumeration item that is manually assigned

enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 3); // true
console.log(Days["Wed"] === 3); // true
console.log(Days[3] === "Sun"); // false
console.log(Days[3] === "Wed"); // true

The enumeration item manually assigned can not be a number. In this case, you need to use type assertion to make tsc ignore type checking (the compiled js is still available):

enum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>"S"};

Enumeration items with manual assignment can also be decimal or negative. At this time, the increment step of subsequent items without manual assignment is still 1

enum Days {Sun = 7, Mon = 1.5, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 7); // true
console.log(Days["Mon"] === 1.5); // true
console.log(Days["Tue"] === 2.5); // true
console.log(Days["Sat"] === 6.5); // true

Constant term and calculated term

There are two types of enumeration items: constant member and calculated member

// Correct example
enum Color {Red, Green, Blue = "blue".length};
// Error example
// If the calculated item is followed by an item that has not been manually assigned, it will report an error because it cannot obtain the initial value
enum Color {Red = "red".length, Green, Blue};
// index.ts(1,33): error TS1061: Enum member must have initializer.
// index.ts(1,40): error TS1061: Enum member must have initializer.

Enumeration members are treated as constants when the following conditions are met:

Does not have an initialization function and the previous enumeration member is a constant. In this case, the value of the current enumeration member is the value of the previous enumeration member plus 1. The first enumeration element is an exception. If it has no initialization method, its initial value is 0.
Enumeration members are initialized with constant enumeration expressions. Constant enumeration expressions are a subset of TypeScript expressions that can be evaluated at compile time. When an expression satisfies one of the following conditions, it is a constant enumeration expression:

Numeric literal

Reference the previously defined constant enumeration member (which can be defined in different enumeration types). If this member is defined in the same enumeration type, it can be referenced with an unqualified name

Parenthesized constant enumeration expression

+, -, ~ unary operators apply to constant enumeration expressions

+, -, *, /,%, <, > >, > >, &, |, ^ binary operator, constant enumeration expression as one of its operands. If the constant enumeration expression evaluates to NaN or Infinity, an error will be reported at the compilation stage

Enumeration members in all other cases are treated as values that need to be calculated.

Constant enumeration

const enum Directions {
    Up,
    Down,
    Left,
    Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

The difference between constant enumeration and ordinary enumeration is that it will be deleted at the compilation stage and cannot contain calculated members.

If a calculated member is included, an error will be reported at the compilation stage:

const enum Color {Red, Green, Blue = "blue".length};
// index.ts(1,38): error TS2474: In 'const' enum declarations member initializer must be constant expression.

External enums (Ambient Enums)

declare enum Directions {
    Up,
    Down,
    Left,
    Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

It is also possible to use declare and const at the same time:

declare const enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

class

Concept of class

- Class( Class): It defines the abstract characteristics of a thing, including its attributes and methods
- Object( Object): Class through new generate
- Object oriented( OOP)Three characteristics of: encapsulation, inheritance and polymorphism
- Encapsulation( Encapsulation): Hide the details of data operation and only expose the external interface. The external caller does not need (and cannot) know the details, and can access the object through the interface provided externally. At the same time, it also ensures that the external cannot arbitrarily change the internal data of the object
- Inherit( Inheritance): Subclasses inherit the parent class. In addition to all the characteristics of the parent class, subclasses also have some more specific characteristics
- Polymorphism( Polymorphism): Different classes are generated by inheritance, and they can have different responses to the same method. such as Cat and Dog All inherited from Animal,But they have achieved their own goals eat method. At this point, for an instance, we do not need to know whether it is Cat still Dog,You can call it directly eat Method, the program will automatically determine how to execute it eat
- Accessor( getter & setter): Used to change the reading and assignment behavior of attributes
- Modifier( Modifiers): Modifiers are keywords that qualify the nature of a member or type. such as public Represents a public property or method
- Abstract class( Abstract Class): Abstract classes are base classes inherited by other classes. Abstract classes are not allowed to be instantiated. Abstract methods in abstract classes must be implemented in subclasses
- Interface( Interfaces): Public attributes or methods between different classes can be abstracted into an interface. Interfaces can be implemented by classes( implements). A class can only inherit from another class, but can implement multiple interfaces

Usage of ES6 class

Properties and methods

Use class to define the class and constructor to define the constructor.

When a new instance is generated through new, the constructor will be called automatically

class Animal {
    public name;
    constructor(name) {
        this.name = name;
    }
    sayHi() {
        return `My name is ${this.name}`;
    }
}
let a = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
Class inheritance

Use the extends keyword to implement inheritance, and use the super keyword in the subclass to call the constructor and method of the parent class

class Cat extends Animal {
  constructor(name) {
    super(name); // Call the constructor(name) of the parent class
    console.log(this.name);
  }
  sayHi() {
    return 'Meow, ' + super.sayHi(); // Call sayHi() of the parent class
  }
}
let c = new Cat('Tom'); // Tom
console.log(c.sayHi()); // Meow, My name is Tom
Accessor

Using getter s and setter s, you can change the assignment and reading behavior of attributes

class Animal {
  constructor(name) {
    this.name = name;
  }
  get name() {
    return 'Jack';
  }
  set name(value) {
    console.log('setter: ' + value);
  }
}
let a = new Animal('Kitty'); // setter: Kitty
a.name = 'Tom'; // setter: Tom
console.log(a.name); // Jack
Static method

Methods decorated with static modifiers are called static methods. They do not need to be instantiated, but are called directly through classes

class Animal {
  static isAnimal(a) {
    return a instanceof Animal;
  }
}
let a = new Animal('Jack');
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function

Usage of ES7 class

Instance properties

The attribute of an instance in ES6 can only be passed through this. In the constructor XXX, which can be defined directly in the class in ES7 proposal

class Animal {
    name = 'Jack';
    constructor() {
        //...
    }
}
let a = new Animal();
console.log(a.name); //Jack
Static properties

In the ES7 proposal, static can be used to define a static attribute

class Animal {
    static num = 42;
    constructor() {
        // ...
    }
}
console.log(Animal.num); //42

Usage of TypeScript class

public private and protected
  • The properties or methods modified by public are public and can be accessed anywhere. By default, all properties and methods are public
  • The property or method of private modification is private and cannot be accessed outside the class that declares it
  • The protected modified attribute or method is protected. It is similar to private, except that it is also allowed to be accessed in subclasses
Parameter properties

Modifiers and readonly can also be used in constructor parameters, which is equivalent to defining the attribute in the class and assigning a value to the attribute to make the code more concise

class Animal {
  // public name: string;
  public constructor(public name) {
    // this.name = name;
  }
}
readonly

Read only attribute keyword, only allowed in attribute declaration or index signature or constructor

class Animal {
  readonly name;
  public constructor(name) {
    this.name = name;
  }
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
// index.ts(10,3): TS2540: Cannot assign to 'name' because it is a read-only property.

It will be more elegant to write like this

class Animal {
  // public readonly name;
  public constructor(public readonly name) {
    // this.name = name;
  }
}
abstract class

Abstract is used to define abstract classes and abstract methods in them

Abstract classes are not allowed to be instantiated, ex:

abstract class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}
let a = new Animal('Jack');
// index.ts(9,11): error TS2511: Cannot create an instance of the abstract class 'Animal'.

Abstract methods in abstract classes must be implemented by subclasses, ex:

abstract class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}
// Subclasses implement abstract classes
class Cat extends Animal {
  public sayHi() {
    console.log(`Meow, My name is ${this.name}`);
  }
}
let cat = new Cat('Tom');

Type of class

Adding TypeScript types to classes is simple, similar to interfaces

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  sayHi(): string {
    return `My name is ${this.name}`;
  }
}
let a: Animal = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack

Classes and interfaces

Class implementation interface

Implementation is an important concept in object-oriented. There can be some common features between different classes. At this time, the features can be extracted into interfaces and implemented with the keyword implements. This feature greatly improves object-oriented flexibility.

For example, a door is a class, and an anti-theft door is a subclass of a door. If the anti-theft door has the function of an alarm, we can simply add an alarm method to the anti-theft door. At this time, if there is another class, car, which also has the function of alarm, you can consider extracting the alarm as an interface, which can be realized by both anti-theft door and car. ex:

interface Alarm {
    alert(): void;
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
    alert() {
        console.log('SecurityDoor alert');
    }
}
class Car implements Alarm {
    alert() {
        console.log('Car alert');
    }
}

A class implements multiple interfaces

interface Alarm {
    alert(): void;
}
interface Light {
    lightOn(): void;
    lightOff(): void;
}
class Car implements Alarm, Light {
    alert() {
        console.log('Car alert');
    }
    lightOn() {
        console.log('Car light on');
    }
    lightOff() {
        console.log('Car light off');
    }
}

Interface inheritance interface

LightableAlarm inherits Alarm and has two new methods, lightOn and lightOff, in addition to the alert method.

interface Alarm {
    alert(): void;
}
interface LightableAlarm extends Alarm {
    lightOn(): void;
    lightOff(): void;
}

Interface inheritance class

In common object-oriented languages, interfaces cannot inherit classes, but they can in TypeScript f

  • Class can be used as a class (new xx creates its instance)
  • Class can be used as a type (use: xx to indicate the type of parameter)
interface PointInstanceType {
    x: number;
    y: number;
}
// Equivalent to
class Point {
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}

When we declare interface Point3d extends Point, Point3d actually inherits the type of the instance of Point class.

In other words, it can be understood that one interface Point3d inherits another interface PointInstanceType.

Therefore, there is no essential difference between "interface inheritance class" and "interface inheritance interface".

class Point {
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}
interface PointInstanceType {
    x: number;
    y: number;
}
// Equivalent to interface point3d extensions pointinstancetype
interface Point3d extends Point {
    z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};

Except that constructors are not included, static properties or static methods are not included (of course, the type of an instance should not include constructors, static properties or static methods)

That is, the type quality created by the declaration class contains the instance properties and instance methods

class Point {
    /** Static properties, coordinate system origin */
    static origin = new Point(0, 0);
    /** Static method, calculate the distance from the origin */
    static distanceToOrigin(p: Point) {
        return Math.sqrt(p.x * p.x + p.y * p.y);
    }
    /** Instance property, value of x axis */
    x: number;
    /** Instance property, value of y-axis */
    y: number;
    /** Constructor */
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
    /** Instance method, print this point */
    printPoint() {
        console.log(this.x, this.y);
    }
}
interface PointInstanceType {
    x: number;
    y: number;
    printPoint(): void;
}
let p1: Point;
let p2: PointInstanceType;

Generics

Generic type refers to a feature that does not specify a specific type in advance when defining a function, interface or class, but specifies a type when using it.

Simple example

function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}
// You can choose to specify or not, and the type inference will be calculated automatically
createArray<string>(3, 'x'); // ['x', 'x', 'x']
createArray(3, 'x'); // ['x', 'x', 'x']

Multiple type parameters

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}
// Tuples used to exchange input
swap([7, 'seven']); // ['seven', 7]

Generic constraints

We use extensions to constrain that generic T must conform to the shape of interface Lengthwise, that is, it must contain the length attribute.

At this time, if the arg passed in does not contain length when calling loggingIdentity, an error will be reported at the compilation stage:

interface Lengthwise {
    length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}
loggingIdentity(7);
// index.ts(10,17): error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'.

Multiple type parameters can also be constrained to each other. T is required to inherit u, which ensures that fields that do not exist in T will not appear on U

function copyFields<T extends U, U>(target: T, source: U): T {
    for (let id in source) {
        target[id] = (<T>source)[id];
    }
    return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 });

generic interface

interface CreateArrayFunc<T> {
    (length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc<any>;
createArray = function<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']

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

Default type of generic parameter

We can specify the default type for type parameters in generics. When using generics, the default type will work when the type parameter is not specified directly in the code and cannot be inferred from the actual value parameter

function createArray<T = string>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

Declaration merge

If two functions, interfaces, or classes with the same name are defined, they are merged into one type

Merging of functions

// Overload defines multiple function types
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

Merging of interfaces

The properties in the interface will be simply merged into one interface when merging

interface Alarm {
    price: number;
    weight: number;
}

The type of the merged property must be unique

interface Alarm {
    price: number;
}
interface Alarm {
    price: string;  // If the type is inconsistent, an error will be reported
    weight: number;
}
// index.ts(5,3): error TS2403: Subsequent variable declarations must have the same type.  Variable 'price' must be of type 'number', but here has type 'string'.

The merging of methods in the interface is the same as that of functions:

interface Alarm {
    price: number;
    alert(s: string): string;
}
interface Alarm {
    weight: number;
    alert(s: string, n: number): string;
}

amount to

interface Alarm {
    price: number;
    weight: number;
    alert(s: string): string;
    alert(s: string, n: number): string;
}

Merge of classes

interface Alarm {
    price: number;
    weight: number;
    alert(s: string): string;
    alert(s: string, n: number): string;
}

engineering

Code check

In January 2019, TypeScirpt officially decided to fully adopt ESLint as a code inspection tool, and created a new project TypeScript ESLint, which provides the parser @ TypeScript ESLint / parser of TypeScript files and related configuration options @ TypeScript ESLint / ESLint plugin.

What is code checking

Code checking is mainly used to find code errors and unify code style.

In JavaScript projects, we generally use ESLint for code checking. It greatly enriches the scope of application through the plug-in feature. After matching TypeScript ESLint, it can even be used to check TypeScript code

Why do I need code checking

After compiling with tsc and checking with eslint respectively, the error message is as follows

var myName = 'Tom';
// eslint error message:
// Unexpected var, use let or const instead.eslint(no-var)
console.log(`My name is ${myNane}`);
// tsc error message:
// Cannot find name 'myNane'. Did you mean 'myName'?
// eslint error message:
// 'myNane' is not defined.eslint(no-undef)
console.log(`My name is ${myName.toStrng()}`);
// tsc error message:
// Property 'toStrng' does not exist on type 'string'. Did you mean 'toString'?
  • There are more advanced syntax let and const in ES6. At this time, it can be checked through eslint, suggesting that we should use let or const instead of var
  • eslint cannot identify which methods exist for myName
  • eslint can find some errors that tsc will not care about and check out some potential problems, so code checking is still very important.

Using ESLint in TypeScript

Install ESLint in the project

npm install --save-dev eslint

ESLint uses Espree for syntax parsing by default and cannot recognize some syntax of typescript, so we need to install @ typescript ESLint / parser instead of the default parser. Don't forget to install typescript at the same time

npm install --save-dev typescript @typescript-eslint/parser

@Typescript eslint / eslint plugin, as a supplement to the default eslint rules, provides some additional rules applicable to ts syntax

npm install --save-dev @typescript-eslint/eslint-plugin

create profile

ESLint needs a configuration file to decide which rules to check. The name of the configuration file is usually eslintrc.js or eslintrc.json

When running ESLint to check a file, it will first try to read the configuration file in the directory of the file, and then look up one level at a time to merge the found configuration as the configuration of the currently checked file

Create one under the root directory of the project eslintrc.js, as follows:

module.exports = {
    parser: '@typescript-eslint/parser',
    plugins: ['@typescript-eslint'],
    rules: {
        // var is prohibited
        'no-var': "error",
        // interface is preferred over type
        '@typescript-eslint/consistent-type-definitions': [
            "error",
            "interface"
        ]
    }
}

In the above configuration, we specify two rules. No VaR is the native rule of ESLint and @ typescript ESLint / consistent type definitions is the new rule of @ typescript ESLint / ESLint plugin.

One of off, warn or error, which indicates shutdown, warning and error

The meanings of shutdown, warning and error reporting are as follows:
  Close: disable this rule
  Warning: error message is output during code check, but it will not affect exit code
  Error reporting: when an error is found, it will not only output error information, but also exit code Will be set to 1 (general) exit code (if it is not 0, there is an error in execution)
Check a ts file

Create a new file index ts

var myName = 'Tom';
type Foo = {};

Execution/ node_modules/.bin/eslint index.ts

Get error report:

/path/to/index.ts
  1:1  error  Unexpected var, use let or const instead  no-var
  2:6  error  Use an `interface` instead of a `type`    @typescript-eslint/consistent-type-definitions

✖ 2 problems (2 errors, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

It's inconvenient to execute such a long script every time. We can Add a script to JSON to create an npm script to simplify this step

{
    "scripts": {
        "eslint": "eslint index.ts"
    }
}

Just execute npm run eslint

Check the ts file of the whole project

The project source file is usually placed in the src directory, so package Instead, the eslint script in JSON checks a directory. Because eslint does not check by default TS suffix file, so you need to add the parameter -- ext ts

{
    "scripts": {
        "eslint": "eslint src --ext .ts"
    }
}

At this point, execute npm run eslint to check all the files in the src directory Files with ts suffix

Integrating ESLint check in VSCode

Plug in name: ESLint

The ESLint plug-in in VSCode does not check by default For ts suffix, you need to add the following configuration in "file = > Preferences = > Settings = > workspace" (you can also create a configuration file. vscode/settings.json in the project root directory):

{
    "eslint.validate": [
        "javascript",
        "javascriptreact",
        "typescript"
    ],
    "typescript.tsdk": "node_modules/typescript/lib"
}

. ts file, move the mouse to the red prompt to see such error information

In addition, automatically repair when saving and modify the configuration file

{
    "editor.codeActionsOnSave": {
        "source.fixAll": true,
        "source.fixAll.eslint": true
    },
    "eslint.validate": [
        "javascript",
        "javascriptreact",
        "typescript"
    ],
    "typescript.tsdk": "node_modules/typescript/lib"
}
Fix formatting errors using Prettier

ESLint includes some code format checks, such as spaces, semicolons, etc. But there is a more advanced tool in the front-end community that can be used to format code, that is Prettier

Prettier focuses on code formatting, reorganizes the code format through syntax analysis, and keeps everyone's code in the same style

Installing Prettier

npm install --save-dev prettier

Create a prettier config. JS file, which contains the configuration items of prettier. Prettier has few configuration items. Here I recommend a configuration rule

// prettier.config.js or .prettierrc.js
module.exports = {
    // A line of up to 100 characters
    printWidth: 100,
    // Indent with 4 spaces
    tabWidth: 4,
    // Use spaces instead of indents
    useTabs: false,
    // A semicolon is required at the end of the line
    semi: true,
    // single quotes 
    singleQuote: true,
    // The key of the object is quoted only when necessary
    quoteProps: 'as-needed',
    // jsx does not use single quotes, but double quotes
    jsxSingleQuote: false,
    // Comma is not required at the end
    trailingComma: 'none',
    // Spaces are required at the beginning and end of braces
    bracketSpacing: true,
    // The inverse angle brackets of jsx tags need to wrap
    jsxBracketSameLine: false,
    // When the arrow function has only one parameter, parentheses are also required
    arrowParens: 'always',
    // The format range of each file is the whole content of the file
    rangeStart: 0,
    rangeEnd: Infinity,
    // You don't need to write @ prettier at the beginning of the file
    requirePragma: false,
    // There is no need to automatically insert @ prettier at the beginning of the file
    insertPragma: false,
    // Use default line break criteria
    proseWrap: 'preserve',
    // Determine whether html should be folded or not according to the display style
    htmlWhitespaceSensitivity: 'css',
    // Line breaks use lf
    endOfLine: 'lf'
};
Use the ESLint configuration of AlloyTeam

There are too many native ESLint rules and @ TypeScript ESLint / ESLint plugin rules, and some native rules are not well supported in TypeScript and need to be disabled

install

npm install --save-dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-alloy

Yes eslintrc.js

module.exports = {
    extends: [
        'alloy',
        'alloy/typescript',
    ],
    env: {
        // Your environment variables (including multiple predefined global variables)
        // Your environments (which contains several predefined global variables)
        //
        // browser: true,
        // node: true,
        // mocha: true,
        // jest: true,
        // jquery: true
    },
    globals: {
        // Your global variable (set to false to indicate that it is not allowed to be reassigned)
        // Your global variables (setting to false means it's not allowed to be reassigned)
        //
        // myGlobal: false
    },
    rules: {
        // Customize your rules
        // Customize your rules
    }
};
Using ESLint to check tsx files

Install eslint plugin react:

npm install --save-dev eslint-plugin-react

package. Scripts in JSON Eslint add tsx suffix

{
    "scripts": {
        "eslint": "eslint src --ext .ts,.tsx"
    }
}

New typescriptreact check in VSCode configuration

{
    "eslint.validate": [
        "typescriptreact"
    ],
}
Conclusion: if you encounter any questions or suggestions, you can leave a comment directly! The author will reply one by one as soon as he sees it

If you think Xiaobai's article is good or helpful to you, look forward to your one click three links 💫!❤️ ni ~

Keywords: Javascript Front-end Programming TypeScript

Added by planethax on Tue, 01 Feb 2022 09:39:52 +0200