TypeScript

持续更新

我们需要在类型的严格性和开发的便利性之间掌握平衡

“reaching the right balance and making the right exceptions is the essence of how successful programming languages are designed.”

基础

非严格模式下 undefined 和 null 可以赋值给其他类型的值,也就是可以作为其它类型的子类型

如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查

typeof 能直接获取元素深层类型

const Message = {
    name: "jimmy",
    age: 18,
    address: {
      province: '四川',
      city: '成都'   
    }
}
type message = typeof Message;
/*
 type message = {
    name: string;
    age: number;
    address: {
        province: string;
        city: string;
    };
}
*/

never类型

never 和 void 之间的区别是void 意味着至少要返回一个 undefined 或者 null ,而 never 意味着不会正常执行到 函数的终点。

! 特殊用法

  • 强制链式调用
// 这里 Error对象定义的stack是可选参数,如果这样写的话编译器会提示
// 出错 TS2532: Object is possibly 'undefined'.
new Error().stack.split('\n');

// 我们确信这个字段100%出现,那么就可以添加!,强调这个字段一定存在
new Error().stack!.split('\n');
  • 用在赋值的内容后时,使null和undefined类型可以赋值给其他类型并通过编译
// 由于x是可选的,因此parma.x的类型为number | undefined,无法传递给number类型的y,因此需要用x!
interface IDemo {
    x?: number
}

let y:number

const demo = (parma: IDemo) => {
    y = parma.x!
    return y
}

? 特殊用法

  • 当使用A对象属性A.B时,如果无法确定A是否为空,则需要用A?.B,表示当A有值的时候才去访问B属性,没有值的时候就不去访问,如果不使用?则会报错
// 由于函数参数可选,因此parma无法确定是否拥有,所以无法正常使用parma.x,使用parma?.x向编译器假设此时parma不为空且为IDemo类型,同时parma?.x无法保证非空,因此使用parma?.x!来保证了整体通过编译
interface IDemo {
    x: number
}

let y:number

const demo = (parma?: IDemo) => {
    y = parma?.x!
    console.log(parma?.x)   // 只是单纯调用属性时就无需!    
    return y
}
    
// 如果使用y = parma!.x!是会报错的,因为当parma为空时,是不拥有x属性的,会报找不到x的错误

支持接口:

一个简单的接口,SquareConfig可以有任意数量的属性,并且只要它们不是colorwidth,那么就无所谓它们的类型是什么。

interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;
}

定义数组

let arr1: number[] = [1,2,3];
let arr2: Array<number> = [1,2,3];
元组,元素数量和各位置元素类型确定,均不可违背
let arr3: [string,number,boolean] = [1,'1',true];

联合类型(Union Types)

表示取值可以为多种类型中的一种。

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法

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

联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型,确定类型后也就可以使用其特有的属性和方法了。

函数类型

支持可选参数

function e(arg1:number,arg2?:number){}

可选参数后面不允许再出现必需参数

允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数,此时上一条限制不再存在

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

关于剩余参数,其实也就相当于多一个数组参数在末尾

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

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

函数定义还支持重载,当输入参数类型不同时按照程序逻辑进行不同的操作

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

类型断言

两种写法

值 as 类型
or
<类型>值

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法。但是通过类型断言我们能在还不确定类型的时候就访问其中一个类型特有的属性或方法

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

即作用为将一个联合类型断言为其中一个类型,同样还有其它作用:将一个父类断言为更加具体的派生类(此种情况下使用 instanceof 可能会更贴切),将任何一个类型断言为 any,将 any 断言为一个具体的类型

class ApiError extends Error {
    code: number = 0;
}
class HttpError extends Error {
    statusCode: number = 200;
}

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

将一个变量断言为 any 可以说是解决 TypeScript 中类型问题的最后一个手段。

它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用 as any

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();

any as 上例中调用完 getCacheData 后把返回值限定为 Cat ,明确后续代码中的 tom 类型。

类型声明比类型断言更加严格,当声明的类型兼容值的类型才使赋值语句成立。而类型断言中等号左边和右边的类型任一兼容对方即可。

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

const animal: Animal = {
    name: 'tom'
};
let tom: Cat = animal;//改为 let tom = animal as Cat; 不会报错

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

类与接口

implements
实现,一个新的类,从父类或者接口实现所有的属性和方法,同时可以重写属性和方法,包含一些新的功能

extends
继承,一个新的接口或者类,从父类或者接口继承所有的属性和方法,不可以重写属性,但可以重写方法。

存在 constructor 时需要在其中调用 super 方法(相当于调用父类的constructor)

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

抽象类:专用于子类实例化和实现其中的抽象方法,本身不可实例化。抽象类还包括抽象属性(不常用)和抽象方法,不会实现内容,留给子类实现。

abstract class Animal{
	abstract add(){};
}

class Dog extends Animal{
    add(a:number,b:number):number{
        return a+b;
    }
}

进阶

泛型

指代任意输入的类型,保持在其它地方使用该类型的一致性

function swap<T, U>(tuple: [T, U]): [U, T] {
    return [tuple[1], tuple[0]];
}

swap([7, 'seven']); // ['seven', 7]

泛型约束可以

reflection

proxy 接口


interface ProxyHandler<T extends object> {
    getPrototypeOf? (target: T): object | null;
    setPrototypeOf? (target: T, v: any): boolean;
    isExtensible? (target: T): boolean;
    preventExtensions? (target: T): boolean;
    getOwnPropertyDescriptor? (target: T, p: PropertyKey): PropertyDescriptor | undefined;
    has? (target: T, p: PropertyKey): boolean;
    get? (target: T, p: PropertyKey, receiver: any): any;
    set? (target: T, p: PropertyKey, value: any, receiver: any): boolean;
    deleteProperty? (target: T, p: PropertyKey): boolean;
    defineProperty? (target: T, p: PropertyKey, attributes: PropertyDescriptor): boolean;
    ownKeys? (target: T): PropertyKey[];
    apply? (target: T, thisArg: any, argArray?: any): any;
    construct? (target: T, argArray: any, newTarget?: any): object;
}

interface ProxyConstructor {
    revocable<T extends object>(target: T, handler: ProxyHandler<T>): { proxy: T; revoke: () => void; };
    new <T extends object>(target: T, handler: ProxyHandler<T>): T;
}
declare var Proxy: ProxyConstructor;

修饰符

static

只属于类自己的,不需要实例化即可引用不能通过new出来的实例访问类的静态变量或方法类中访问自己的静态属性,不能用this,只能用类名。

class Test {
 	static myName: string = "lle";
  	static changeName(): void {
    	console.log("=========>>>", "hello");
  	}
  	
  	this.myName = "change name";  ----------->>>>>>>>>> ❌❌❌ 不可以
  	Test.myName = "change name"; ----------->>>>> ✔️✔️✔️这样就是OK的
}

Test.changeName();  ------------->>>>>>>>>> ✔️✔️✔️这样就是OK的


let instance = new Test();
instance.changName();  ----------->>>>>>>>>> ❌❌❌ 不可以

Utility Types

/**
 * Make all properties in T optional
 */
type Partial<T> = {
    [P in keyof T]?: T[P];
};

/**
 * Make all properties in T readonly
 */
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

/**
 * From T, pick a set of properties whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

/**
 * Construct a type with a set of properties K of type T
 */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

Partial<Type>

构造一个类型,其中 Type 的所有属性都设置为可选。此实用工具将返回一个表示给定 type 的所有子集的类型。

type Coord = Partial<Record<'x' | 'y', number>>;

// 等同于
type Coord = {
	x?: number;
	y?: number;
}

Pick

type Coord = Record<'x' | 'y', number>;
type CoordX = Pick<Coord, 'x'>;

// 等用于
type CoordX = {
	x: number;
}

Required<Type>

partial的反义,返回给定 type 的超集

Readonly<Type>

仅读不可更改类型。只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候

type Coord = Readonly<Record<'x' | 'y', number>>;

// 等同于
type Coord = {
    readonly x: number;
    readonly y: number;
}

// 如果进行了修改,则会报错:
const c: Coord = { x: 1, y: 1 };
c.x = 2; // Error: Cannot assign to 'x' because it is a read-only property.

Record<Keys, Type>

构造一个对象类型,其属性键为 Keys,其属性值为 Type。此实用工具可用于将一种类型的属性映射到另一种类型。

type Coord = Record<'x' | 'y', number>;

// 等同于
type Coord = {
	x: number;
	y: number;
}

编译配置

直接使用以下命令对指定文件进行编译会生成在当前目录下,忽略tsconfig 的配置文件

tsc 指定文件

tsconfig配置信息

target –

就是TypeScript文件编译后生成的javascript文件里的语法应该遵循哪个JavaScript的版本。可选项为:"ES5""ES6"/ "ES2015""ES2016""ES2017""ESNext"

module –

就是你的TypeScript文件中的module,采用何种方式实现,可选项为:"None""CommonJS""AMD""System""UMD""ES6""ES2015"。