TypeScript introductory notes

challenge

  • Need to understand concepts that front-end engineers may not be familiar with, such as Interfaces, Generics, Classes, Enums, etc.
  • It may increase some development costs in the short term. After all, you need to write more type definitions. However, for a project that requires long-term maintenance, TypeScript can reduce its maintenance costs.
  • Integrating into the build process requires some work
  • It may not be perfect in combination with some libraries

Install && compile

npm install -g typescript
tsc hello.ts

TypeScript playground address: https://www.typescriptlang.org/play/#In
fact, new Boolean() returns a Boolean object; directly calling Boolean can also return a boolean type :
define the boolean value correctly:

let isDone: boolean = false;
let createdByBoolean: boolean = Boolean(1);

number defines the numeric type:

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八进制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;

String type :

let myName: string = 'Tom';
let myAge: number = 25;

// 模板字符串
let sentence: string = `Hello, my name is ${
      
      myName}.
I'll be ${
      
      myAge + 1} years old next month.`;

JavaScript does not have the concept of void. In TypeScript, void can be used to represent a function without any return value: it is useless to
declare a variable of type void , because you can only assign it to undefined and null:

function alertName(): void {
    
    
    alert('My name is Tom');
}
let unusable: void = undefined;

In TypeScript, you can use null and undefined to define these two primitive data types:
the difference from void is that undefined and null are subtypes of all types. That is to say, variables of type undefined can be assigned to variables of type number:

let u: undefined = undefined;
let n: null = null;
// 这样不会报错
let num: number = undefined;
// 这样也不会报错
let u: undefined;
let num: number = u;

If it is a common type, it is not allowed to change the type during the assignment process:
but if it is of the any type , it is allowed to be assigned to any type.
Access to any property and method on any value is allowed, even if it is not an object; it
can be considered that after declaring a variable as any value, any operation on it, the type of content returned is any value.
If a variable does not specify its type when it is declared, it will be recognized as an arbitrary value type:

TypeScript will infer a type when there is no explicit type specified. This is type inference .
If there is no assignment at the time of definition, regardless of whether there is assignment afterwards, it will be inferred to be of type any without being type checked at all:

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

Union types use | to separate each type. The meaning of let myFavoriteNumber: string | number is that the type of myFavoriteNumber is allowed to be string or number, but not other types.
When TypeScript is not sure which type the variable of a union type is, it can only access the properties or methods common to all types of the union type:

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'.

When a variable of a union type is assigned, a type will be inferred according to the rules of type inference:

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // 编译时报错

// index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.

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

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

let tom: Person = {
    
    
    name: 'Tom',
    age: 25
};

In the above example, we define an interface Person, and then define a variable tom whose type is Person. In this way, we constrain the shape of tom to be consistent with the interface Person.
It is not allowed to define a variable that has fewer attributes than the interface, and it is also not allowed to have more attributes. When assigning values, the shape of the variable must be consistent with the shape of the interface.
Sometimes we don’t want to match a shape exactly, so we can use optional attributes . At this time, it’s still not allowed to add undefined attributes:

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

let tom: Person = {
    
    
    name: 'Tom'
};

Sometimes we want an interface to allow arbitrary attributes , we can use the following methods:

interface Person {
    
    
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    
    
    name: 'Tom',
    gender: 'male'
};

Use [propName: string] to define any property to take the value of type string.
It should be noted that once any attribute is defined, the type of the determined attribute and optional attribute must be a subset of its type.
Only one arbitrary attribute can be defined in an interface. If there are multiple types of attributes in the interface, you can use the union type in any attribute.
readonly defines read-only attributes:

interface Person {
    
    
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    
    
    id: 89757,
    name: 'Tom',
    gender: 'male'
};

tom.id = 9527;

// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

In the above example, after the attribute id defined by readonly is initialized, it is assigned again, so an error is reported.
In TypeScript, there are many ways to define the array type , which is more flexible.

  • "Type + square brackets" notation§
let fibonacci: number[] = [1, 1, 2, 3, 5];
  • Array generics§
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
  • Use interfaces to represent arrays§
interface NumberArray {
    
    
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
  • Array-like §
    arguments is actually an array-like, which can’t be described in the way of ordinary arrays, but should use interfaces: in this example, in addition to constraining that when the type of the index is a number, the type of the value must be a number other than , Which also restricts it to have two attributes, length and callee. In fact, commonly used class arrays have their own interface definitions, such as IArguments, NodeList, HTMLCollection, etc.:
function sum() {
    
    
    let args: {
    
    
        [index: number]: number;
        length: number;
        callee: Function;
    } = arguments;
}
function sum() {
    
    
    let args: IArguments = arguments;
}

A function has input and output. To constrain it in TypeScript, both input and output need to be considered. The type definition of the function declaration is relatively simple:
inputting redundant (or less than required) parameters is not allowed of

function sum(x: number, y: number): number {
    
    
    return x + y;
}

Function expression

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

In the type definition of TypeScript, => is used to indicate the definition of a function. The left side is the input type, which needs to be enclosed in parentheses, and the right side is the output type.

You can use the interface to define the shape that a function needs to conform to:

interface SearchFunc {
    
    
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    
    
    return source.search(subString) !== -1;
}

Use? To indicate optional parameters; it should be noted that optional parameters must be followed by required parameters. In other words, no required parameters are allowed after 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');

In ES6, we allow default values ​​to be added to function parameters. TypeScript will recognize parameters with default values ​​as optional parameters; at this time, it is not restricted 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');

The rest parameter can only be the last parameter:

function push(array: any[], ...items: any[]) {
    
    
    items.forEach(function(item) {
    
    
        array.push(item);
    });
}

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

Use overloading to define multiple reverse 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('');
    }
}

In the above example, we have repeatedly defined the function reverse several times. The first few times are function definitions, and the last time is function implementation. In the code prompt of the editor, you can see the first two prompts correctly.

Note that TypeScript will match the first function definition first, so if multiple function definitions have an inclusion relationship, you need to write the precise definition first.
Type Assertion can be used to manually specify the type of a value. Syntax: value as type or <type> value The
former must be used in the tsx syntax (the ts version of React's jsx syntax), that is, the value as type. The grammar of the form is a ReactNode in tsx. In addition to a type assertion in ts, it may also represent a generic type.
Therefore, it is recommended that you use the value as type syntax when using type assertions.
Use type assertion to assert animal as Fish:

interface Cat {
    
    
    name: string;
    run(): void;
}
interface Fish {
    
    
    name: string;
    swim(): void;
}

function isFish(animal: Cat | Fish) {
    
    
    if (typeof (animal as Fish).swim === 'function') {
    
    
        return true;
    }
    return false;
}

Type assertions can only "cheat" the TypeScript compiler and cannot avoid runtime errors. On the contrary, misuse of type assertions may lead to runtime errors:

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`

The above example will not report an error when compiling, but it will report an error at runtime: the reason is (animal as Fish).swim() This code hides the possibility that the animal may be Cat, and the animal is directly asserted as Fish, and TypeScript is compiled The processor trusts our assertion, so there is no compilation error when calling swim(). In short, you must be extra careful when using type assertions, and try to avoid calling methods or referencing deep properties after asserting to reduce unnecessary runtime errors.

In some cases, ApiError and HttpError are not a real class, but just a TypeScript interface (interface), an interface is a type, not a real value, it will be deleted in the compilation result, of course, you cannot use instanceof to do it Judged at runtime:

interface ApiError extends Error {
    
    
    code: number;
}
interface HttpError extends Error {
    
    
    statusCode: number;
}

function isApiError(error: Error) {
    
    
    if (error instanceof ApiError) {
    
    
        return true;
    }
    return false;
}

// index.ts:9:26 - error TS2693: 'ApiError' only refers to a type, but is being used as a value here.

At this time, you can only use type assertion to determine whether the passed parameter is ApiError by judging whether the code attribute exists:

interface ApiError extends Error {
    
    
    code: number;
}
interface HttpError extends Error {
    
    
    statusCode: number;
}

function isApiError(error: Error) {
    
    
    if (typeof (error as ApiError).code === 'number') {
    
    
        return true;
    }
    return false;
}
  • The union type can be asserted as one of the types
  • The 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.
    In fact, the first four cases are special cases of the last one.
    Type assertion is not a type conversion, it will not really affect the type of the variable.
interface Animal {
    
    
    name: string;
}
interface Cat {
    
    
    name: string;
    run(): void;
}

const animal: Animal = {
    
    
    name: 'tom'
};
let tom: Cat = animal;

// index.ts:12:5 - error TS2741: Property 'run' is missing in type 'Animal' but required in type 'Cat'.

It is not allowed to assign animal to tom of type Cat.

This is easy to understand. Animal can be regarded as the parent class of Cat. Of course, you cannot assign an instance of the parent class to a variable whose type is a subclass.

In depth, their core differences are:

The animal is asserted to be Cat, and
animal can be assigned to tom only if Animal is compatible with Cat or
Cat is compatible with Animal. However, Cat is not compatible with Animal.

Use declare var to define the type

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

jQuery('#foo');

The declaration file must be suffixed with .d.ts.

// src/jQuery.d.ts

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

Generally speaking, ts will parse all *.ts files in the project, of course, it also includes files ending with .d.ts. So when we put jQuery.d.ts into the project, all other *.ts files can get the type definition of jQuery.
Third-party declaration file §Of
course, the jQuery declaration file does not need to be defined by us. The community has already defined it for us: jQuery in DefinitelyTyped.

We can download it and use it directly, but it is more recommended to use @types to uniformly manage the declaration files of third-party libraries.

The way to use @types is very simple, just use npm to install the corresponding declaration module. Take jQuery as an example:

npm install @types/jquery --save-dev

Guess you like

Origin blog.csdn.net/taozi550185271/article/details/107630607