深入浅出TypeTypeScript
为什么要学TypeScript?
TS在社区的活跃度越来越高
TypeScript vs JavaScript
TS的优势:
-
类型化思维方式,使开发更严谨,提前发现错误,减少改Bug时间
-
类型系统提高了代码可读性,维护和重构代码更加容易
-
补充了接口、枚举等开发大型应用时JS缺失的功能
TS带来了什么?
- 类型安全
- 下一代JS特性
- 完善的工具链
TS不仅仅是一门语言,更是生产力工具
TS推荐
- Awesome TypeScript:TS开源教程及应用
- Byte Tech:TS&React:React + TypeScript开发模式介绍
- Typescript Playground:TS到JS在线编译
TS基础
基础类型
- boolean、number、string
- 枚举enum
- any、unknown、void
- never
- 数组类型[]
- 元祖类型tupte
函数类型
定义:TS定义函数类型时要定义输入参数类型和输出类型
输入参数:参数支持可选参数和默认参数
输出参数:输出可以自动推断,没有返回值时,默认为void类型
函数重载:名称相同但参数不同,可以通过重载支持多种类型
pickCard
方法根据传入参数的不同会返回两种不同的类型。 如果传入的是代表纸牌的对象,函数作用是从中抓一张牌。 如果用户想抓牌,我们告诉他抓到了什么牌。 但是这怎么在类型系统里表示呢。 方法是为同一个函数提供多个函数类型定义来进行函数重载。 编译器会根据这个列表去处理函数的调用。 重载pickCard函数:
typescript
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
为了让编译器能够选择正确的检查类型,它与JavaScript里的处理流程相似。 它查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。
interface
定义:接口是为了定义对象类型
特点:
- 可选属性:?
接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在。带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?
符号。
可选属性在应用"option bags"模式时很常用,即给函数传入的参数对象中只有部分属性赋值了。下面是应用了"option bags"的例子:
typescript
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): {color: string; area: number} {
let newSquare = {color: "white", area: 100};
if (config.color) {
newSquare.color = config.color;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({color: "black"});
- 只读属性:readonly
一些对象属性只能在对象刚刚创建的时候修改其值。
typescript
interface Point {
readonly x: number;
readonly y: number;
}
- 可以描述函数类型
- 可以描述自定义属性
总结:接口非常灵活duck typing
类
定义:写法和JS差不多,增加了一些定义
特点: 增加了public、private、protected修饰符 抽象类:
- 只能被继承,不能被实例化
在TypeScript里,我们可以使用常用的面向对象模式。当然,基于类的程序设计中最基本的模式是允许使用继承来扩展现有的类。
typescript
class Animal {
name:string;
constructor(theName: string) { this.name = theName; }
move (distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
- 作为基类,抽象方法必须被子类实现
- interface约束类,使用implements关键字
TS进阶
高级类型
- 联合类型 |
- 交叉类型 &
- 类型断言
- 类型别名(type VS interface) 定义:给类型起个别名
相同点:
- 都可以定义对象或函数
- 都允许继承
差异点:
- interface是TS用来定义对象,type是用来定义别名方便使用;
- type可以定义基本类型,interface不行
- interface可以合并重复声明,type不行
泛型-什么时候需要泛型
官方定义: 软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
应用场景: 定义一个print函数,这个函数功能是把传入的参数打印出来,再返回这个参数,传入参数的类型是string,函数返回类型为string
基本使用
基本定义: 泛型的语法是<>里面写类型参数,一般用T表示; 例子:identity函数。 这个函数会返回任何传入它的值。 你可以把这个函数当成是echo
命令。
不用泛型的话,这个函数可能是下面这样:
typescript
function identity(arg: number): number {
return arg;
}
因此,我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了 类型变量,它是一种特殊的变量,只用于表示类型而不是值。
typescript
function identity<T>(arg: T): T {
return arg;
}
使用时有两种方法指定类型:
- 定义要使用的类型
- 通过TS类型推断,自动推导类型
- 泛型的作用是临时占位,之后通过传来的类型进行推导
基础操作符
- typeof:获取类型
- keyof:获取所有键
- in:遍历枚举类型
- T[K]:索引访问
- extends:泛型约束
常用工作类型
- Partial:将类型属性变为可选
- Required:将类型属性变为必选
- Readonly:将类型属性变为只读
- Pick、Record......
TypeScript实战
声明文件
-
declare:三方库需要类型声明文件
-
.d.ts:声明文件定义
-
@types:三方库TS类型包
-
tsconfig.json:定义TS的配置
如果一个目录下存在一个
tsconfig.json
文件,那么它意味着这个目录是TypeScript项目的根目录。tsconfig.json
文件中指定了用来编译这个项目的根文件和编译选项。tsconfig.json
示例文件: -
使用
"files"
属性
typescript
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"outFile": "../../built/local/tsc.js",
"sourceMap": true
},
"files": [
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"emitter.ts",
"program.ts",
"commandLineParser.ts",
"tsc.ts",
"diagnosticInformationMap.generated.ts"
]
}
泛型约束后端口类型
泛型约束后端口类型可以是任何类型,只要满足特定的约束条件。在泛型中,可以使用接口、类或其他泛型类型作为约束条件。
例如,假设有一个泛型函数,用于判断两个对象是否相等:
typescript
Copyfunction isEqual<T>(a: T, b: T): boolean {
return a === b;
}
如果要对泛型进行约束,可以使用接口来定义约束条件。例如,可以定义一个接口 Comparable
,表示具有比较相等性的类型:
typescript
Copyinterface Comparable {
equals(other: any): boolean;
}
function isEqual<T extends Comparable>(a: T, b: T): boolean {
return a.equals(b);
}
在这个例子中,isEqual
函数的泛型参数 T
被约束为实现了 Comparable
接口的类型。这样,只有实现了 equals
方法的类型才能被传递给 isEqual
函数。