Skip to Content
🎉🎉🎉欢迎来到我的空间🎉🎉🎉
前端TypeScript

开篇

  1. 什么是TypeScript(TS)?

TypeScript简称TS

TSJS之间的关系其实就是Less/SassCSS之间的关系

就像Less/Sass是对CSS进行扩展一样,TS也是对JS进行扩展

就像Less/Sass最终会转换成CSS一样,我们编写好的TS代码最终也会转换成JS

  1. 为什么需要TypeScript?

因为JavaScript是弱类型,很多错误只有在运行时才会被发现

TypeScript是强类型,它提供了一套静态检测机制,可以帮助我们在编译时就发现错误

  1. TypeScript特点

    支持最新的JavaScript新特性

    支持代码静态检查

    支持注入C、C++、Java、Go等后端语言中的特性

    (枚举、泛型、类型转换、命名空间、声明文件、类、接口等)

数据类型

基础数据类型

TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用

// 数值类型 number // 定义了一个名称叫做value1的变量,这个变量将来只能存储数值类型的数据 let value1:number; value1 = 123; // 布尔类型 boolean // 定义了一个名称叫做value2的变量,这个变量将来只能存储布尔类型的数据 let value2:boolean; value2 = true; // 字符串类型 string // 定义了一个名称叫做value3的变量,这个变量将来只能存储字符串类型的数据 let value3:string; value3 = 'hello world!'; value3 = `你好世界!`

数组和元祖类型

// 数组类型 // 方式一 // 表示定义了一个名叫arr1的数组,这个数组将来只能用来存储数值类型的数据 let arr1:Array<number>; arr1 = [1, 3, 5]; // 方式二 // 表示定义了一个名叫arr2的数组,这个数组将来只能用来存储字符串类型的数据 let arr2:string[]; arr2 = ['2', '4', '6']; // 联合类型 // 表示定义了一个名叫arr3的数组,这个数组将来只能用来存储字符串类型的数据和字符串类型的数据 let arr3:(number | string)[]; arr3 = [1, '2', 3]; // 任意类型 // 表示定义了一个名叫arr4的数组,这个数组将来可以存储任意类型的数据 let arr4:any[]; arr4 = [1, '2', true]; // 元祖类型 // TS中的元祖类型其实就是数组类型的扩展 // 元祖用于保存定长定数据类型的数据 // 表示定义了一个名叫arr5的数组,这个数组将来可以存储3个元素,第一个元素必须是字符串类型,第二个元素必须是数字类型,第三个元素必须是布尔类型 let arr5:[string, number, boolean]; arr5 = ['1', 2, false];

枚举类型

枚举类型是TSJS扩展的一种类型,在原生的JS中是没有枚举类型的

枚举用于表示固定的几个取值

例如:一年只有四季、人的性别

注意点:

  • TS中的枚举底层实现的本质其实就是数值类型,所以赋值一个数值不会报错
  • TS中的枚举类型的取值,默认是从上至下从0开始递增的
  • 虽然默认是从0开始递增的,但是我们也可以手动的指定枚举的取值的值
  • 如果手动指定了前面枚举的取值,那么后面的枚举值的取值会根据前面的值来递增
  • 如果手动指定了后面枚举的取值,那么前面的枚举值的取值不会受到影响
  • 我们还可以同时修改多个枚举值的取值,如果同时修改了多个,那么修改的是什么最后就是什么
enum Gender{ Male, Female, } // 定义了一个名称叫做value的变量,这个变量中只能保存Male或者Female let value:Gender; value = Gender.Male; value = Gender.Female; // 枚举数值的实现原理 let Gender = {}; Gender[Gender["Male"] = 0] = "Male"; console.log(Gender["Male"]); // 0 console.log(Gender[0]); // Male

any类型

any表示任意类型,当我们不清楚某个值的具体类型的时候我们就可以使用any

一般用于定义一些通用型比较强的变量,或者用于保存从其它框架中获取不确定类型的值

TS中任何类型都可以赋值给any类型

// 定义了一个可以保存任意类型数据的变量 let value:any; value = 123; value = 'hello world!'; value = false; value = [1, 3, 5];

void类型

void类型与any正好相反,表示没有任何类型,一般用于函数返回值

TS中只有nullundefined可以赋值给void类型

注意点:

  • nullundefined是所有类型的子类型,所以我们可以将nullundefined赋值给任意类型
// 定义了一个没有返回值的函数 function test():void{ console.log('hello world!'); } test();

never类型

表示的是那些永不存在的值的类型

一般用于抛出异常或根本不可能有返回值的函数

function demo():never{ throw new Error('报错了') } demo(); function demo2():never{ while(true){} } demo2();

object类型

表示一个对象

let obj:object; obj = {name: 'zs', age: 18}

类型断言

TS中的类型断言和其它编程语言的类型转换很像,可以将一种类型强制转换成另一种类型。

类型断言就是告诉编辑器,你不要帮我们检查了,相信我,我知道自己在干什么。

注意点:

​ 企业开发中推荐使用as来进行类型转换(类型断言)

​ 因为第一种方式有兼容性问题,在使用到JSX的时候兼容性不是很好

/* * 例如: * 我们拿到了一个any类型的变量,但是我们明确的知道这个变量中保存的是字符串类型 * 此时我们就可以通过类型断言告诉编译器,这个变量是一个字符串类型 * 此时我们就可以通过类型断言将any类型转换成string类型,使用字符串类型中相关的方法了 * */ let str:any = 'hello world!'; // 方式一 // let len = (<string>str) // console.log(len.length); // 方式二 let len = (str as string); console.log(len.length);

接口类型

接口也是一种类型,也是用来约束使用者的

// 需求:要求定义一个函数输出一个人完整的姓名,这个姓必须是字符串,这个人的命也必须是一个字符串 interface FullName{ firstName: string, lastName: string, } let obj = { firstName: 'hello', lastName: 'world' } function say({firstName, lastName}:FullName):void{ console.log(`我的姓名是:${firstName}${lastName}`) } say(obj);

可选属性

让对象属性可选是否传入

// 对象属性可以少一个或者少多个 interface FullName{ firstName: string; lastName: string; middleName?: string; // 可选属性 [propName: string]: any; // 索引签名 } function say({firstName, lastName}:FullName){ console.log(firstName, lastName); } say({firstName: 'hello', lastName: 'world'}) // 对象属性可以多一个或者多多个 // 方式一:类型断言 say({firstName: 'hello', lastName: 'world', middleName: '!', abc: 'abc'} as FullName) // 方式二:使用变量(不推荐) let obj = {firstName: 'hello', lastName: 'world', middleName: '!', abc: 'abc'}; say(obj); // 方式三:索引签名 say({firstName: 'hello', lastName: 'world', middleName: '!', abc: 'abc'});

索引签名

索引签名用于描述那些“通过索引得到的类型”,比如arr[0]obj['key']

// 索引签名 interface FullName { [propName: string]: string; } // 只要key和value满足索引签名的限制即可,无论有多少个无所谓 let obj:FullName = { firstName: 'John', lastName: 'Doe', false: 'hello', // 无论key是什么类型最终都会自动转换成字符串类型,所以没有报错 } interface StringArray { [propName: number]: string; } let arr:StringArray = { 0: 'a', 1: 'b', 2: 'c', } let arr2:StringArray = ['a', 'b', 'c'];

只读属性

让对象属性只能在对象刚刚创建的时候修改值

// 只读属性 interface FullName { firstName: string; readonly lastName: string; } let myName:FullName = { firstName: 'hello', lastName: 'world', } // 报错: Cannot assign to lastName because it is a read-only property // myName.lastName = '世界'; // TS内部对只读属性进行了扩展,扩展出来了一个只读数组 let arr:ReadonlyArray<string> = ['a', 'b', 'c']; console.log(arr[0]); // 报错: Index signature in type readonly string[] only permits reading // arr[0] = 'abc'

函数接口

除了可以通过接口来限定对象以外,我们还可以使用接口来限定函数

// 函数接口 interface SumInterface { (a: number, b: number): number } let sum:SumInterface = function (x: number, y: number):number{ return x + y } sum(1, 2);

混合类型接口

// 要求定义一个函数实现变量累加 interface CountInterface { (): void; count: number; } let getCounter = (function():CountInterface{ /* CountInterface接口要求数据既要是一个没有参数返回值的函数 又要是一个拥有count属性的对象 fn作为函数的时候符合接口中函数接口的限定 ():void fn作为对象时符合接口中对象接口的限定 {count:number} * */ let fn = <CountInterface>function(){ fn.count++ console.log(fn.count) } fn.count = 0; return fn })(); getCounter(); getCounter(); getCounter();

接口继承

TS中的接口和JS中的类一样是可以继承的

// 接口的继承 interface LengthInterface { length: number } interface WidthInterface { width: number } interface HeightInterface { height: number } interface RectInterface extends LengthInterface, WidthInterface, HeightInterface { color: string } let rect:RectInterface = { length: 10, width: 20, height: 30, color: 'red' }

函数

// TS中的函数 // TS中的函数大部分和JS相同 // 命名函数 function say1(name: string):void{ console.log(name); } // 匿名函数 let say2 = function(name: string):void{ console.log(name); } // 箭头函数 let say3 = (name: string):void => { console.log(name); }

函数声明

// 函数声明 // 声明一个函数 type AddFun = (a: number, b: number) => number; let add:AddFun = function(x, y){ return x + y; } let res = add(10, 20);

函数重载

函数的重载就是同名的函数可以根据不同的参数来实现不同的功能

// 函数重载 /* function getArray(x:number):number[]{ let arr = []; for(let i = 0; i < x; i++){ arr.push(i) } return arr } function getArray(str:string):string[]{ return str.split('') } */ // 定义函数重载 function getArray(x:number):number[]; function getArray(str:string):string[]; // 实现函数的重载 function getArray(x:any):any{ if(typeof x === 'number'){ let arr = []; for(let i = 0; i < x; i++){ arr.push(i) } return arr }else{ return x.split('') } } let res = getArray(10); console.log(res); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] let res2 = getArray('hello world!'); console.log(res2); // ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!']

可选参数

// 可选参数 // 需求:要求定义一个函数可以实现2个数或者3个数的加法 /* function add(x: number, y: number, z?: number):number{ return x + y + (z || 0); } add(10, 20); */ // 可选参数可以配合函数重载一起使用,可以让函数重载变得更加强大 function add(x: number, y: number):number; function add(x: number, y: number, z: number):number; function add(x: number, y: number, z?: number):number{ return x + y + (z || 0); } add(10, 20, 30);

默认参数

// 默认参数 function add(x:number, y:number=20){ return x + y; } add(10); add(10, 30);

剩余参数

// 剩余参数 function add(x:number, ...args:number[]):void{ console.log(x); console.log(args) } add(10,20,30,40,50);

泛型

在编写代码的时候我们既要考虑代码的健壮性,又要考虑代码的灵活性和可重性

通过TS的静态检测能让我们编写的代码更加健壮,但是在变得健壮的同时却丢失了灵活性和重用性

所以为了解决这个问题TS推出了泛型的概念

通过泛型不仅可以让我们的代码变得更加健壮,还能让我们的代码在变得健壮的同时保持灵活性和可重用性

// 泛型 // 需求:定义一个创建数组的方法,可以创建出指定长度的数组,并且可以用任意指定的内容填充这个数组 let getArr = <T>(value:T, items:number):T[] => { return new Array(items).fill(value); } // 需求:要有代码提示,如果写错了要在编译的时候报错 // let arr = getArr(6, 3); let arr = getArr('abcd', 3); console.log(arr); arr.map(item => item.length)

泛型约束

默认情况下我们可以指定泛型为任意类型

但是有些情况下我们需要指定的类型满足某些条件后才能指定

那么这个时候我们就可以使用泛型约束

// 泛型约束 interface LengthInterface { length:number; } let getArr = <T extends LengthInterface>(value:T, items:number = 5):T[] => { return new Array(items).fill(value); } let res = getArr('abcdefg', 3); res.map(item => item.length) console.log(res)

约束中使用类型参数

一个范型被另一个范型约束,就叫做范型约束中使用类型参数

// 泛型约束使用类型参数 // 需求:定义一个函数用于根据指定的key获取对象的value let getProps = <T, K extends keyof T>(obj: T, key: K):any => { return obj[key]; } let obj = { name: 'why', age: 18 } let res = getProps(obj, 'name');

TS中的类和ES6中的类几乎是一样的

export class Person { // 和ES6区别,需要先定义实例属性,才能够使用实例属性 // 实例属性 实例子方法 name:string; age:number; constructor(name:string, age:number){ this.name = name; this.age = age; } say():void{ console.log(`我的名字是${this.name},我的年龄是${this.age}`); } // 静态属性 静态方法 static food:string; static eat():void{ console.log(`我正在吃${this.food}`) } } let p = new Person('张三', 18); p.say(); Person.food = '雪糕'; Person.eat(); // 继承 /* class Student extends Person{ constructor(name:string, age:number){ super(name, age); } } let stu = new Student('李四', 19); stu.say(); Student.food = '冰淇淋'; Student.eat(); */ // 覆写 class Student extends Person{ book:string; constructor(name:string, age:number, book:string){ super(name, age); this.book = book; } say():void{ console.log(`覆写之后的:我叫${this.name},我今年${this.age}岁,我正在看${this.book}`); } static eat():void{ console.log(`覆写之后的:吃${Student.food}`); } } let stu = new Student('王五', 20, '三体'); stu.say(); Student.food = '瘦猪肉'; Student.eat();

属性修饰符

public(公开的): 如果使用public来修饰属性,那么表示这个属性是公开的

​ 可以在类的内部使用,也可以在子类中使用,也可以在外部使用

protected(受保护的):

​ 如果使用protected来修饰属性,那么表示这个属性是受保护的 ​ 可以在类的内部使用,可以在子类中使用,不可以再外部使用

private(似有的):

​ 如果使用private来修饰属性,那么表示这个属性是私有的

​ 可以在类的内部中使用

readonly(只读的)

export class Person { public name:string; // 属性“age”受保护,只能在类“Person”及其子类中访问 protected age:number; // 属性“gender”为私有属性,只能在类“Person”中访问 private gender:string; // 无法为“height”赋值,因为它是只读属性 readonly height:number; constructor(name:string, age:number, gender:string, height:number){ this.name = name; this.age = age; this.gender = gender; this.height = height; } say():void{ console.log(`我的名字是${this.name},我的年龄是${this.age},我的性别是${this.gender},我的身高是${this.height}`); } } class Student extends Person{ constructor(name:string, age:number, gender:string, height:number){ super(name, age, gender, height); } say():void{ // console.log(`覆写之后的:我叫${this.name},我今年${this.age}岁,我的身高是${this.height}`); } } let p = new Person('张三', 18, 'male', 180); p.say(); // p.height = 190; // 报错 let s = new Student('李四', 18, 'female', 176); s.say(); // console.log(s.name); // console.log(s.age); // 报错

方法修饰符

方法修饰符和属性修饰符一样

// 奇淫技巧 // 需求:有一个基类,所有的子类都需要继承这个基类,但是我们不希望别人通过这个基类来创建对象 class Person{ name:string; age:number; protected constructor(name:string, age:number){ this.name = name; this.age = age; } say():void{ console.log(`我的名字是${this.name},我的年龄是${this.age}`); } } class Student extends Person{ constructor(name:string, age:number){ super(name, age); } say():void{ console.log(`我的名字是${this.name},我的年龄是${this.age}`); } } // let p = new Person('张三', 18); let s = new Student('李四', 18);

可选属性

和接口中的可选属性一样,可传可不传的属性

// 可选属性 class Person { name:string; age?:number; constructor(name:string, age?:number){ this.name = name; this.age = age; } } let p = new Person('张三');

参数属性

// 参数属性 /* class Person { name:string; age:number; constructor(name:string, age:number) { this.name = name; this.age = age; } } */ // 简化上面代码 class Person { constructor(public name:string, public age:number) { } } let p = new Person('hello', 18); console.log(p);

存取器

通过getters/setters来截取对对象成员的访问

// 存取器 class Person{ private _age:number = 0; set age(val){ console.log('进入了set age方法'); if(val < 0){ throw new Error('输入的数字不能小于0'); } this._age = val; } get age():number{ console.log('进入了get age方法') return this._age; } } let p = new Person(); // p.age = 10; // p.set(10); p.age = 34; console.log(p.age);

抽象类

  1. 什么是抽象类

    抽象类是专门用于定义那些不希望被外界直接创建的类的

    抽象类一般用于定义基类

    抽象类和接口一样用于约束子类

  2. 抽象类和接口区别

    接口中只能定义约束,不能定义具体实现

    尔抽象类中既可以定义约束,又可以定义具体实现

export {} // 抽象类 abstract class Person{ abstract name:string; abstract say():void; eat():void { console.log(`${this.name}正在吃东西`); } } class Student extends Person{ name:string = '张三'; say():void{ console.log(`我的名字是${this.name}`) } } let stu = new Student(); stu.say(); stu.eat();

类和接口

// 类实现接口 /* interface PersonInterface { name:string; say():void; } // 只要实现的某一个接口,那么就必须实现接口中所有的属性和方法 class Person implements PersonInterface{ name:string = '张三'; say(): void { console.log(`我的名字是${this.name}`); } } let p = new Person(); p.say(); */ // 接口继承类 class Person { protected name:string = '张三'; age:number = 18; protected say():void{ console.log(`name:${this.name},age:${this.age}`); } } // 只要一个接口继承了某个类,那么就会继承这个类中所有的属性和方法 // 但是只会继承属性和方法的声明,不会继承属性和方法的实现 // 如果接口继承的类中包含了protected的属性和方法,那么就只有这个类的子类才能实现这个接口 interface PersonInterface extends Person{ book:string; } class Student extends Person implements PersonInterface{ book:string = '《人间》'; name:string = '李四'; age:number = 20; say():void{ console.log(`name:${this.name},age:${this.age},book:${this.book}`); } } let stu = new Student(); stu.say();

泛型

// 泛型类 class Cache<T>{ arr:T[] = []; add(value:T):T{ this.arr.push(value); return value; } all():T[]{ return this.arr; } } let cache = new Cache<number>(); cache.add(1); cache.add(3); cache.add(5); console.log(cache.all());

接口合并现象

当我们定义了多个同名的接口时,多个接口的内容会自动合并

// 接口合并 interface TestInterface{ name:string; } interface TestInterface{ age:number; } class Person implements TestInterface{ name:string; age:number; }

枚举

TS中支持两种枚举,一种是数字枚举,一种是字符串枚举

// 数字枚举 // 默认情况下是数字枚举 /* enum Gender{ Male, Female } */ // 数字枚举的取值默认是从0开始递增 // 数字枚举的取值可以是字面量,也可以是常量,也可以是计算的结果 /* const num = 666; function getNum(){ return 234; } enum Gender{ // Male = 6, // Male = num, // Female Male = getNum(), // 如果使用了计算结果给前面的枚举赋值了,那么后面的枚举值也需要手动的赋值 Female = 235 } console.log(Gender, 'gender'); */ // 枚举反向映射 // 可以根据枚举值获取到原始值 // 可以根据原始值获取到枚举值 /* enum Gender{ Male, Female } console.log(Gender.Male); console.log(Gender[0]); */ // 字符串枚举 // 和数字枚举不一样,字符串枚举不能使用常量或者计算结果给枚举赋值 /* enum Gender{ Male = '男', // 如果使用了字符串给前面的枚举赋值了,那么后面的枚举值也需要手动的赋值 Female = '女' } */ // 异构枚举 // 枚举中既包含数字又包含字符串,我们称之为异构枚举 // 如果是字符串枚举,那么无法通过原始值获取到枚举值 enum Gender{ Male = '男', Female = 1 } console.log(Gender.Male); console.log(Gender['男']); // undefined console.log(Gender.Female); console.log(Gender[1]);

成员类型

// 枚举成员类型 enum Gender{ Male, Female, } interface TestInterface { age: Gender.Male } class Person implements TestInterface{ age: Gender.Male; // age: 0; // 由于数字枚举的本质就是数值,所以这里少写一个数值也不会报错.如果是字符串类型,那么只能是枚举的值,不能是其它的值 }

联合类型

把枚举类型当作一个联合类型来使用

// 联合枚举类型 enum Gender { Male, Female } interface GenderInterface { age: Gender; } class Person implements GenderInterface{ // age: Gender.Male; age: Gender.Female; }

运行时枚举

枚举在编译之后是一个真实存储的对象,所以可以在运行时使用

而像接口这种只是用来做静态检查的代码,编译之后是不存在的

interface TestInterface{ name:string; age:number; } enum Gender{ Male, Female } /* 编译后代码 var Gender; (function (Gender) { Gender[Gender["Male"] = 0] = "Male"; Gender[Gender["Female"] = 1] = "Female"; })(Gender || (Gender = {})); */

常量枚举

普通枚举和常量枚举的区别

普通枚举会生成真实存在的对象

常量枚举不会成圣真实存在的对象,而是利用枚举成员的值直接替换使用到的地方

enum Gender1{ Male, Female } console.log(Gender1.Male === 0); const enum Gender2{ Male, Female } console.log(Gender2.Male === 0); /* 编译后代码 var Gender1; (function (Gender1) { Gender1[Gender1["Male"] = 0] = "Male"; Gender1[Gender1["Female"] = 1] = "Female"; })(Gender1 || (Gender1 = {})); console.log(Gender1.Male === 0); console.log(0 /* Gender2.Male */ === 0); */

类型推断

  1. 什么是自动类型推断?

    不用明确告诉编译器具体是什么类型,编译器就知道是什么类型

    • 根据初始值自动推断

      // 根据初始值自动推断 // let value:number = 123; let value = 123; value = 234; // let arr:(number | string) = [1, 'a']; let arr = [1, 'a']; arr = [1, 5, 6];
    • 根据上下文类型自动推断

      // 根据上下文自动推断 window.onmousedown = (event) => { console.log(event.target) }

兼容性

基本兼容性

interface TestInterface{ name:string } let p1 = {name:'张三'}; let p2 = {age:18}; let p3 = {name:'张三', age:18}; let t:TestInterface; t = p1; // t = p2; // 报错:类型 "{ age: number; }" 中缺少属性 "name",但类型 "TestInterface" 中需要该属性 t = p3; interface Test2Interface{ name:string; children: { age:number; } } let p4 = {name:'张三', children:{age:18}}; let p5 = {name:'李四', children:{age: 'abc'}}; let t2:Test2Interface; t2 = p4; // t2 = p5; // 报从:会递归检查

函数兼容性

export {} // 参数个数 /* let fn1 = (x:number, y:number) => {}; let fn2 = (x:number) => {}; fn1 = fn2; // fn2 = fn1; // 不可将参数多的赋给参数少的 */ // 参数类型 /* let fn1 = (x:number) => {}; let fn2 = (x:number) => {}; let fn3 = (x:string) => {}; fn1 = fn2; fn2 = fn1; // fn1 = fn3; // 必须一模一样 // fn3 = fn1; */ // 返回值类型 /* let fn1 = ():number => 123; let fn2 = ():number => 234; let fn3 = ():string => 'abc'; fn1 = fn2; fn2 = fn1; // fn1 = fn3; // 必须一模一样 // fn3 = fn1 */ // 函数协变 // 参数逆变 /* let fn1 = (x:(number | string)) => {}; let fn2 = (x:number) => {}; // fn1 = fn2; // 错误:不能将参数类型更具体的函数赋给参数类型更宽泛的函数 fn2 = fn1; // 正确:可以将参数类型更宽泛的函数赋给参数类型更具体的函数 */ // 返回值双向协变 /* let fn1 = (x:boolean):(number | string) => x ? 123 : 'abc'; let fn2 = (x:boolean):number => 456; fn1 = fn2; // 可以将返回值是具体类型的赋值给联合类型 // fn2 = fn1; // 不能将返回值是联合类型的赋值给具体类型的 */ // 函数重载 function add(x:number, y:number):number; function add(x:string, y:string):string; function add(x, y){ return x + y; } function sub(x:number, y:number):number; function sub(x, y){ return x - y; } // let fn = add; // fn = sub; // 不能将重载少的赋值给多的 let fn = sub; fn = add; // 可以将重载多的赋值给重载少的

枚举兼容性

export {} // 数字枚举与数值兼容 /* enum Gender{ Male, Female } let value:Gender; value = Gender.Female; value = 1; */ // 数字枚举与数字枚举不兼容 /* enum Gender1{ Male, Female } enum Gender2{ Male, Female } let value:Gender1; value = Gender1.Male; // value = Gender2.Male; // 不能将类型“Gender2.Male”分配给类型“Gender1”。 */ // 字符串枚举与字符串枚举不兼容 enum Gender{ Male = '男', Female = '女' } let value:Gender; value = Gender.Male; value = '男'; // 不能将类型“"男"”分配给类型“Gender”

类兼容性

export {} // 只比较实例成员,不比较类的构造函数和静态成员 /* class Person{ public name:string; public static age:number; constructor(name:string, age:number){} } class Animal{ public name:string; constructor(name:string){} } let p:Person; let a:Animal; // p = a; a = p; // 可多不可少 */ // 类的私有属性和受保护属性会影响兼容性 class Person { protected name:string; } class Animal { protected name:string; } let p:Person; let a:Animal; // p = a; // a = p;

泛型兼容性

// 泛型只影响使用的部分,不会影响声明的部分 interface TestInterface<T> { age:T; // 用到了范型才会影响到兼容性 } let t1:TestInterface<number>; // age number let t2:TestInterface<string>; // age string // t1 = t2; // t2 = t1;

交叉类型

格式:type1 & type2 & ..

交叉类型是将多个类型合并为一个类型

// 交叉类型 let mergeFn = <T, U>(arg1:T, arg2:U):(T & U) => { let res = {} as (T & U); res = Object.assign({}, arg1, arg2); return res } let res = mergeFn({name: '张三'}, {age: 18}); console.log(res);

联合类型

格式:type1 | type2 | ..

联合类型是多个类型中的任意一个类型

// 联合类型 let value:(string | number); value = 123; value = 'abc';

类型保护

对于联合类型的变量,在使用时如何确切的告诉编译器它是哪一种类型

export {} // 类型保护 let getRandomValue = ():(string | number) => { let num = Math.random(); return (num >= 0.5) ? 'abc' : 123.123; } let value = getRandomValue(); // 通过类型断言可以确切告诉编辑器当前变量是什么类型 // 比较麻烦,冗余代码比较多 /* if((value as string).length){ console.log((value as string).length); }else{ console.log((value as number).toFixed(2)); } */ // 通过类型保护函数,这个函数返回一个布尔值 /* function isString(value:string | number):value is string{ return typeof value === 'string'; } if(isString(value)){ console.log(value.length); }else{ console.log(value.toFixed()); } */ // 除了可以通过类型保护来告诉编译器以外,还可以使用typeof来实现类型保护 // 只能对 number/string/boolean/symbol类型有效 /* if(typeof value === 'string'){ console.log(value.length); }else{ console.log(value.toFixed()); } */ // 除了通过typeof来判断类型,也可以使用instanceof来判断类型 class Person{ name:string = 'hello'; } class Animal{ age:number = 18; } let getRandomObject = ():(Person | Animal) => { let num = Math.random(); return (num >= 0.5) ? new Person() : new Animal(); } let obj = getRandomObject(); if(obj instanceof Person){ console.log(obj.name); }else{ console.log(obj.age); }

null和undefined

TypeScript具有两种特殊的类型,null和undefined,它们分别具有值null和undefined

默认情况下我们可以将null和undefined赋值给任意类型

默认情况下null和undefined也可以相互赋值

在企业开发总如果不想把null和undefined赋值给其它类型,那我们可以开启strictNullChecks

  • 如果开启了strictNullChecks,还想把null和undefined赋值给其它类型
  • 我们就必须在声明的时候使用联合类型
  • 对于可选属性和可选参数而言,如果开启了strictNullChecks,那么默认情况下数据类型就是联合类型,就是当前的类型+undefined类型
let value:(number | undefined | null); value = undefined; value = null; class Person{ name?:string; }

去除null和undefined检测

// 去除null和undefined检测 function getLength(value:(string | undefined | null)){ return () => { // return value.length // 报错 // return (value || '').length // return (value as string).length return value!.length } } let res = getLength('hello world!');

类型别名

类型别名就是给一个类型起一个新名字,但是它们都代表同一个类型

例如:本命叫张三,外号可以叫小三,小三是张三的别名,张三和小三都表示同一个人

// 类型别名 // type MyString = string; // let value:MyString; // value = 'hello world'; // 类型别名也可以使用范型 // type MyString<T> = { // x: T, // y: T // } // let value:MyString<number>; // value = {x:1,y:2}; // 可以在类型别名中的属性使用自己 // type MyType = { // name: string, // children?: MyType // } // let value:MyType; // value = { // name: '1-0', // children: { // name: '2-1' // } // } // 接口和类型别名是互相兼容的 type MyType = { name: string; } interface MyInterface { name: string; } let value1:MyType = {name: 'zs'}; let value2:MyInterface = {name: 'ls'}; // value1 = value2; value2 = value1;

类型别名和接口异同

// 接口和类型别名异同 // 相同点 // 都可以描述属性和方法 // type MyType = { // name:string; // say():void; // } // interface MyInterface{ // name:string; // say():void; // } // 都允许扩展 // interface MyInterface{ // name:string; // say():void; // } // interface MyInterface2 extends MyInterface{ // age:number; // } // let value:MyInterface2 = { // name:'', // age:0, // say(){} // } // type MyType = { // name:string; // say():void; // } // type MyType2 = MyType & { // age:number; // } // let value2:MyType2 = { // name:'', // age:0, // say(){} // } // 不同点 // type可以声明基本类型别名,联合类型,元祖等类型,interface不能 type MyType1 = boolean; type MyType2 = string | number; type MyType3 = [string,number, boolean]; // type不会自动合并 interface MyInterface{ name:string; } interface MyInterface{ age: number } let value:MyInterface = { name: 'zs', age: 18 }

字面量类型

字面量就是源代码中一个固定的值

例如数值字面量:1,2,3 … 例如字符串字面量:‘a’, ‘abc’, …

在TS中我们可以把字面量作为具体的类型来使用

当使用字面量作为具体的类型时,该类型的取值就必须是该字面量的值

// 字面量 type MyNum = 1; let vlaue1: MyNum = 1;

可辨识联合

具有共同的可辨识特征。

一个类型别名,包含了具有共同的可辨识特征的类型的联合。

// 可辨识联合 interface Square{ kind: "square", size: number } interface Rectangle{ kind: "rectangle", width: number, height: number } interface Circle{ kind: "circle", radius: number } // Shape就是一个可辨识联合 // 它的取值是一个联合,这个联合的取值都有一个共同的可辨识特征 type Shape = (Square | Rectangle | Circle); function aera(s: Shape){ switch(s.kind){ case "square": return s.size * s.size; case "rectangle": return s.width * s.height; case "circle": return Math.PI * s.radius ** 2; // **是ES中推出的幂运算符 } }

可辨识联合完整性检查

在企业开发中如果相对可辨识联合的完整性进行检查,那么我们可以使用

方式一:给函数添加返回值 + 开启strictNullChecks

方式二:添加添加default + never

// 可辨识联合 interface Square{ kind: "square", size: number } interface Rectangle{ kind: "rectangle", width: number, height: number } interface Circle{ kind: "circle", radius: number } function MyNever(x:never):never{ throw new Error("可辨识联合处理不完整" + x); } type Shape = (Square | Rectangle | Circle); function aera(s: Shape){ switch(s.kind){ case "square": return s.size * s.size; case "rectangle": return s.width * s.height; case "circle": return Math.PI * s.radius ** 2; // **是ES中推出的幂运算符 default: return MyNever(s) } }

索引访问操作符

通过[]索引访问操作符,我们就能得到某个索引的类型

class Person { name:string; age:number; } type MyType = Person['name']; // 应用场景 // 需求:获取指定对象,部分属性的值,放到数组中返回 let obj = { name: 'lnj', age: 18, gender: true, } function getValues<T, K extends keyof T>(obj:T, keys:K[]):T[K][] { let arr = [] as T[K][]; keys.forEach(key => { arr.push(obj[key]); }) return arr } let res = getValues(obj, ['name', 'age']); // 索引访问操作符注意点 // 不会返回null/undefined/never类型 interface TestInterface{ a:string, b:number, c:boolean, d:symbol, e:null, f:undefined, g:never } type MyType2 = TestInterface[keyof TestInterface]

映射类型

根据旧的类型创建出新的类型,称之为映射类型

Readonly

复制后的类型为只读属性

// 映射类型 // Readonly // Partial interface TestInterface { name:string, age:number, } type MyType = Readonly<TestInterface>; // 只读 type MyType2 = Partial<TestInterface>; // 可选 type MyType3 = Readonly<Partial<TestInterface>> // 只读 + 可选

Partial

复制后的属性为可选属性

// 映射类型 // Partial interface TestInterface { name:string, age:number, } type MyType = Readonly<TestInterface>; // 只读 type MyType2 = Partial<TestInterface>; // 可选 type MyType3 = Readonly<Partial<TestInterface>> // 只读 + 可选

Pick

将原有的类型中的部分内容映射到新的类型中

// Pick interface TestInterface { name:string, age:number, } type MyType = Pick<TestInterface, "name">; // {name:string}

Record

将一个类型的属性值都映射到另一个类型上并创造一个新的类型

// Record type Animal = 'person' | 'dog' | 'cat'; interface TestInterface { name:string, age:number, } type MyType = Record<Animal, TestInterface>; let res:MyType = { person: { name: 'zs', age: 18 }, dog: { name: 'ls', age: 18 }, cat: { name: 'ww', age: 18 } }

映射类型进行推断

对于Readonly,Partial和Pick的映射类型,我们可以对映射之后的类型进行拆包

还原映射之前的类型,这种操作我们称之为拆包

interface MyInterface{ name:string, age:number } type MyType<T> = { +readonly [P in keyof T]: T[P]; } type test = MyType<MyInterface>; type UnMyType<T> = { -readonly [P in keyof T]: T[P]; } type test2 = UnMyType<test>;

条件类型(三目运算)

判断前面一个类型是否跟后面一个类型或者继承于后面一个类型

如果是就返回一个结果,如果不是就返回第二个结果

语法:T extends U ? X : Y

// 条件类型(三目运算) type MyType<T> = T extends string ? T : any; type res = MyType<boolean>;

分布式条件类型

被检测类型是一个联合类型的时候,该条件类型就称之为分布式条件类型

type MyType<T> = T extends any ? T : never; type res = MyType<string | number | boolean>;

Exclude

// 从T中剔除可以复制给U的类型 Exclude type res = Exclude<string | number | boolean, number>; // type res = string | number

Extract

// 提取T中可以复制给U的类型 Extract type res = Extract<string | number | boolean, number>; // type res = number;

NonNullable

// 从T中剔除Null和undefined NonNullable type res = NonNullable<string | null | undefined | number>; // type res = string | number

ReturnType

// 获取函数返回值类型 ReturnType type res = ReturnType<(() => string)> // type res = string

ConstructorParameters

// 获取一个类的构造函数组成的元祖类型。 ConstructorParameters class Person{ constructor(name:string, age:number) {} } type res = ConstructorParameters<typeof Person>; // type res = [name:string, age:number]

Parameters

// 获得函数的参数类型组成的元祖类型。 Parameters function say(name:string, age:number, gender:boolean){} type res = Parameters<typeof say>; // type res = [name:string, age:number, gender:boolean];

infer

// infer关键字 // 条件类型提供了一个infer关键字,可以让我们在条件类型中定义新的类型 // 需求:定义一个类型,如果传入的是数组,就返回数组的元素类型,如果传入的是普通类型,则直接返回这个类型 // type MyType<T> = T extends any[] ? T[number] : T; // type res = MyType<string[]>; // type res = string; // type res = MyType<number>; // type res = number; type MyType<T> = T extends Array<infer U> ? U : T; // type res = MyType<string[]>; // type res = string; type res = MyType<number>; // type res = number;

unknown

// unknown // unknown类型是TS3.0中新增的一个类型,被称作安全的any // 1.任何类型都可以复制给unknown类型 // let value:unknown; // value = 123; // value = 'abc'; // value = true; // 2.如果没有类型断言或基于控制流的类型细化,那么不能将unknown类型赋值给其它类型 // let value1:unknown = 123; // let value2:number; // value2 = value1 as number; // if(typeof value1 === "number") { // value2 = value1; // } // 3.如果没有类型断言或基于控制流的类型细化,那么不能在unknown类型上进行任何操作 // let value1:unknown = 123; // (value1 as number)++ // if(typeof value1 === 'number'){ // value1++ // } // 4.只能对unknown类型进行 相等或不等操作,不能进行其它操作(因为其它操作没有意义) // let value1:unknown = 123; // let value2:unknown = 123; // console.log(value1 === value2); // console.log(value1 !== value2); // 5.unknown与任何其它类型组成的交叉类型最后都是其它类型 // type MyType = unknown & number; // type MyType = number; // type MyType2 = string & unknown; // type MyType2 = string; // 6.unknown除了与any以外,与其它任意类型组成的联合类型最后都是unknown类型 // type MyType = number | unknown; // type MyType = unknown; // type MyType2 = unknown | string; // type MyType = unknown; // 7.never类型是unknown类型的子类型 // type MyType = never extends unknown ? true : false; // true; // 8.keyof unknown等于never // type MyType = keyof unknown; // type MyType = never; // 9.unknown类型的值不能访问属性,方法,创建实例 // class Person{ // name: string = 'zs'; // say():void{ // console.log(`name=${this.name}`); // } // } // let p:unknown = new Person(); // console.log(p.name); // Property name does not exist on type unknown // p.say(); // Property name does not exist on type unknown // 10.使用映射类型时,如果遍历的是unknown类型,那么不会映射任何属性 type MyType<T> = { [P in keyof T]: any; } type res = MyType<unknown>; // type res = {}

模块系统

// 60-index.ts // ES6的模块和Node的模块是不兼容的,所以TS为了兼容两者就推出了 // export = xxx; // import xxx = require('path'); import obj = require('./61-index'); console.log(obj); // 61-index.ts export const obj = { name: 'zs', age: 20 }