什么是 TypeScript?
TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
简而言之,TypeScript是JavaScript的超集,具有可选的类型并可以编译为纯JavaScript。从技术上讲TypeScript就是具有静态类型的 JavaScript 。
为什么需要 TypeScript?
- TypeScript 具有类型系统,可以捕获在JavaScript中可能出现的类型错误。
- TypeScript 还具有 ECMAScript 6(ES6)的所有功能,如类,模块,迭代器和生成器。
- TypeScript 还具有 JavaScript 中没有的许多功能,如接口,类型断言,类型保护,类型别名,命名空间和模块重定向。
文档链接
https://www.tslang.cn/docs/handbook/basic-types.html
TypeScript 安装
TypeScript 安装非常简单。
首先,你需要安装 Node.js。Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时。
安装 Node.js 后,你就可以使用 npm 命令安装 TypeScript。
1 | npm install -g typescript |
TypeScript 编译
TypeScript 编译器将 TypeScript 代码转换为 JavaScript 代码。
TypeScript 编译器在命令行上运行,如下所示:
1 | tsc hello.ts |
生成 tsconfig.json 配置文件
1 | tsc --init |
开启了监听模式 实时监测app.ts 同步到app.js
1 | tsc app.ts -w |
接着执行
1 | node app.js |
TypeScript 编译选项
TypeScript 编译器有多个编译选项。
1 | tsc --help |
TypeScript 编译选项示例
1 | tsc --target es5 --module commonjs hello.ts |
TypeScript 编译选项
选项 | 描述 |
---|---|
–help | 显示帮助消息 |
–version | 显示版本号 |
–declaration | 生成相应的 .d.ts 文件 |
–watch | 监视文件改变 |
–removeComments | 删除所有注释 |
–noImplicitAny | 在表达式和声明上有隐含的 any类型时报错 |
–noImplicitThis | this 表达式的类型为 any 时报错 |
–alwaysStrict | 以严格模式检查每个模块,并把“use strict”添加到每个输出文件 |
–strictNullChecks | 启用严格的 Null 检查 |
–strictFunctionTypes | 启用严格函数类型检查 |
–strictPropertyInitialization | 启用严格检查属性初始化 |
–strictBindCallApply | 启用严格检查 call、bind和apply的参数匹配和返回值 |
–strictNullPropertyInitialization | 启用严格检查属性初始化 |
–strict | 启用所有严格检查选项 |
–noEmitOnError | 在有错误的情况下不生成输出文件 |
–noEmit | 不生成输出文件 |
–preserveConstEnums | 保留使用 const 和 enum 声明的枚举 |
–allowJs | 允许编译 javascript 文件 |
–checkJs | 报告 javascript 文件中的错误 |
–nocheckJs | 不检查 javascript 文件 |
–allowSyntheticDefaultImports | 允许从没有设置默认导出的模块中默认导入 |
–noImplicitReturns | 函数表达式和函数声明必须有返回值 |
–noFallthroughCasesInSwitch | 报告 switch 语句的 fallthrough 错误 |
–noUnusedLocals | 报告未使用的本地变量 |
–noUnusedParameters | 报告未使用的参数 |
TypeScript 基础类型
js的数据类型
1 | number 、string、 null 、 undefined、 boolean、 object、 function、 array |
ts新增的数据类型1
any任意类型 enum 枚举 联合类型 字面量类型 unknown void
Boolean 类型
声明一个变量flag,同时指定它的类型为boolean1
const flag: boolean = true;
Number 类型
声明一个变量a,同时指定它的类型为number1
const count: number = 10;
String 类型
声明一个变量str,同时指定它的类型为string1
let name: string = "树哥";
Array 类型
声明一个变量arr,同时指定它的类型为array1
let arr: number[] = [1, 2, 3];
Enum 枚举类型
枚举类型用于定义数值集合,使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。,如周一到周日,方位上下左右等
- 普通枚举
1 | enum Color { |
以上代码转化后的代码为:1
2
3
4
5
6
7
8
9var Color;
(function (Color) {
Color[Color["Red"] = 0] = "Red";
Color[Color["Green"] = 1] = "Green";
Color[Color["Blue"] = 2] = "Blue";
})(Color || (Color = {}));
var c = Color.Red;
var c1 = Color.Green;
console.log(c, c1);
初始值默认为 0 其余的成员会会按顺序自动增长 可以理解为数组下标
- 设置初始值
1 | enum Color { |
- 字符串枚举
1 | enum Color { |
- 常量枚举
使用 const 关键字修饰的枚举,
常量枚举与普通枚举的区别是,整个枚举会在编译阶段被删除 我们可以看下编译之后的效果
1 | const enum Color { |
Array 数组类型
对数组类型的定义有两种方式:
1 | const arr: number[] = [1, 2, 3]; |
元组(tuple)类型
上面数组类型的方式,只能定义出内部全为同种类型的数组。对于内部不同类型的数组可以使用元组类型来定义
元组( Tuple )表示一个已知数量和类型的数组,可以理解为他是一种特殊的数组
元组,就是固定长度的数组 语法:[类型, 类型, 类型]
const tuple: [number, string] = [1, "zhangmazi"];
需要注意的是,元组类型只能表示一个已知元素数量和类型的数组,长度已指定,越界访问会提示错误。例如,一个数组中可能有多种类型,数量和类型都不确定,那就直接
any[]
。
null、undefined 类型
默认情况下 null
和 undefined
是所有类型的子类型。 也就是说你可以把 null
和 undefined
赋值给其他类型。1
2
3
4
5
6let a: undefined = undefined;
let b: null = null;
let str: string = 'zhangmazi';
str = null; // 编译正确
str = undefined; // 编译正确
如果你在tsconfig.json
指定了”strictNullChecks”:true ,即开启严格模式后, null
和 undefined
只能赋值给 void
和它们各自的类型。
null
和undefined
只能给它们自己的类型赋值
1
2
3
4
5
6
7
8
9
10 // 启用 --strictNullChecks
let x: number;
x = 1; // 编译正确
x = undefined; // 编译错误
x = null; // 编译错误
// 但是 undefined 可以给 void 赋值
let c:void = undefined // 编译正确
let d:void = null // 编译错误
any 类型
- any会跳过类型检查器对值的检查,任何值都可以赋值给any类型 any类型就是js(建议不要用)
- ts会有命名规范,不能命名为any
- any 表示的是任意类型,一个变量设置类型为any后相当于对该变量关闭了TS的类型检测
- 声明变量如果不指定类型,则TS解析器会自动判断变量的类型为any (隐式的any)
- any是可以赋值任何类型
1 | let value: any = 1; |
void 类型
void
意思就是无效的, 一般只用在函数上,告诉别人这个函数没有返回值。
1 | function sayHello(): void { |
never 类型
抛出异常
never 类型表示的是那些永不存在的值的类型。
例如never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型
值会永不存在的两种情况:
- 如果一个函数执行时抛出了异常,那么这个函数永远不存在返回值(因为抛出异常会直接中断程序运行,这使得程序运行不到返回值那一步,即具有不可达的终点,也就永不存在返回了)
- 函数中执行无限循环的代码(死循环),使得程序永远无法运行到函数返回值那一步,永不存在返回。
1 | // 异常 |
Unknown 类型
unknown
与 any
一样,所有类型都可以分配给 unknown
:unknown
表示未知类型的值
只能赋值给unkonwn
any
1
2
3let value: unknown = 1;
value = "zhangmazi"; // 编译正确
value = false; // 编译正确
unknown
与any
的最大区别是:
任何类型的值可以赋值给
any
,同时any
类型的值也可以赋值给任何类型。unknown
任何类型的值都可以赋值给它,但它只能赋值给unknown
和any
对象类型
这里所说的对象类型,就是我们常说的函数、{}、数组、类
object、 Object 、 {} 类型
- object
object
类型用于表示所有的非原始类型,即我们不能把number
、string
、boolean
、symbol
等 原始类型赋值给object
。在严格模式下,null
和undefined
类型也不能赋给object
。
1 |
|
- Object
大Object
代表所有拥有toString
、hasOwnProperty
方法的类型 所以所有原始类型、非原始类型都可以赋给Object
(严格模式下null
和undefined
不可以)
1 | let bigObject: Object; |
- {}
{} 空对象类型和大 Object 一样 也是表示原始类型和非原始类型的集合
类
在 TypeScript 中,我们通过 Class
关键字来定义一个类
1 | class Person { |
数组
1 | const arr: number[] = [1, 2, 3]; |
函数
函数声明
1 | function add(x: number, y: number): number { |
函数表达式
1 | let add = function (x: number, y: number): number { |
接口定义函数
1 | interface Add{ |
可选参数
1 | function Add(x: number, y: number, z?: number): number { |
默认参数
1 | function add(x: number, y: number = 0): number { |
剩余参数
1 | function add(...numbers: number[]): number { |
函数重载
函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。1
2
3
4
5function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any {
return x + y;
}
上面示例中,我们给同一个函数提供多个函数类型定义,从而实现函数的重载
函数重载真正执行的是同名函数最后定义的函数体 在最后一个函数体定义之前全都属于函数类型定义 不能写具体的函数实现方法,只能定义类型
类型推论
如果没有明确的指定类型,那么 TypeScript 会依照类型推论的规则推断出一个类型。1
2
3
4
5
6let x = 1;
x = true; // 报错
// 上面的代码等价于
let x: number = 1;
x = true; // 报错
通过上述示例我们可以看出,我们没有给 x 指定明确类型的时候,typescript 会推断出 x 的类型是 number。
而如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:1
2
3let x;
x = 1; // 编译正确
x = true; // 编译正确
类型断言
某些情况下,我们可能比typescript更加清楚的知道某个变量的类型,所以我们可能希望手动指定一个值的类型
类型断言有两种方式:
尖括号语法
1
2
3let str = "my name is zhangsan"
let strLength: number = (<string>str).length
console.log(strLength)as语法
1
2
3let str = "my name is zhangsan"
let strLength: number = (str as string).length
console.log(strLength)
非空断言
在上下文中当类型检查器无法断定类型时,可以使用缀表达式操作符 ! 进行断言操作对象是非 null 和非 undefined 的类型,即x!的值不会为 null 或 undefined
1 | let user: string | null | undefined; |
确定赋值断言
1 | let value:number |
我们定义了变量, 没有赋值就使用,则会报错
通过 let x!: number; 确定赋值断言,TypeScript 编译器就会知道该属性会被明确地赋值。
1 | let value!:number |
联合类型
联合类型用|
分隔,表示取值可以为多种类型中的一种
可以使用|
来连接多个类型(联合类型)1
2
3
4let n:string|number
n = 'to be or not to be'
n = 1
console.log(n) // 1
类型别名
类型别名用来给一个类型起个新名字。它只是起了一个新名字,并没有创建新类型。类型别名常用于联合类型。
1 | type count = number | number[]; |
交叉类型
交叉类型就是跟联合类型相反,用&
操作符表示,交叉类型就是两个类型必须存在1
2
3
4
5
6
7
8
9
10
11
12
13
14interface IpersonA{
name: string,
age: number
}
interface IpersonB {
name: string,
gender: string
}
let person: IpersonA & IpersonB = {
name: "师爷",
age: 18,
gender: "男"
};
person
即是 IpersonA
类型,又是 IpersonB
类型
注意:交叉类型取的多个类型的并集,但是如果key相同但是类型不同,则该key为 never
类型
1 | interface IpersonA { |
类型守卫
类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。 换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。
换句话说:类型守卫是运行时检查,确保一个值在所要类型的范围内。
目前主要有四种的方式来实现类型保护:
· in 关键字
1 | interface InObj1 { |
· typeof 关键字
1 | function isTypeof( val: string | number): string{ |
typeof 只支持:typeof ‘x’ === ‘typeName’ 和 typeof ‘x’ !== ‘typeName’,x 必须是 ‘number’, ‘string’, ‘boolean’, ‘symbol’。
· instanceof
1 | function creatDate(date: Date | string){ |
· 自定义类型保护的类型谓词
1 | function isNumber(num: any): num is number { |
接口
我们使用接口来定义对象的类型。接口是对象的状态(属性)和行为(方法)的抽象(描述)
简单理解就是:为我们的代码提供一种约定
我们使用关键字 interface
来声明接口
1 | interface Person { |
我们定义了一个接口 Person,接着定义了一个变量 song,它的类型是 Person。这样,我们就约束了 song 的形状必须和接口 Person 一致。
接口一般首字母大写。(当然挺多人也习惯 I 大写字母开头,用来表示这是一个接口)
设置接口可选|只读
1 | interface Person { |
可选属性,我们最常见的使用情况是,不确定这个参数是否会传,或者存在。
只读属性用于限制只能在对象刚刚创建的时候修改其值。此外 TypeScript 还提供了 ReadonlyArray 类型,它与 Array 相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改。
索引签名
有时候我们希望一个接口中除了包含必选和可选属性之外,还允许有其他的任意属性,这时我们可以使用 索引签名 的形式来满足上述要求。
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集1
2
3
4
5
6
7
8
9
10
11
12interface Person {
name: string
[prop: string]: any; // prop字段必须是 string类型 or number类型。 值是any类型,也就是任意的
}
let song:Person = {
name: 'song'
}
song.sex = '男'
// song[true] = true // 类型“true”不能作为索引类型使用。ts(2538)
song[1] = false
console.log(song) // { '1': false, name: 'song', sex: '男', flag: true }
接口与类型别名的区别
实际上,在大多数的情况下使用接口类型和类型别名的效果等价,但是在某些特定的场景下这两者还是存在很大区别。
TypeScript 的核心原则之一是对值所具有的结构进行类型检查。 而接口的作用就是为这些类型命名和为你的代码或第三方代码定义数据模型。
type(类型别名)会给一个类型起个新名字。 type 有时和 interface 很像,但是可以作用于原始值(基本类型),联合类型,元组以及其它任何你需要手写的类型。起别名不会新建一个类型 - 它创建了一个新名字来引用那个类型。给基本类型起别名通常没什么用,尽管可以做为文档的一种形式使用。
- 接口和类型别名都可以用来描述对象或函数的类型,只是语法不同
1 | type MyType = { |
- 都允许扩展
interface 用 extends 来实现扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16interface MyInterface {
name: string;
say(): void;
}
interface MyInterface2 extends MyInterface {
sex: string;
}
let person:MyInterface2 = {
name:'树哥',
sex:'男',
say(): void {
console.log("hello 啊,树哥!");
}
}type 使用 & 实现扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type MyType = {
name:string;
say(): void;
}
type MyType2 = MyType & {
sex:string;
}
let value: MyType2 = {
name:'树哥',
sex:'男',
say(): void {
console.log("hello 啊,树哥!");
}
}
不同点
interface能够合并声明,而type不行
1
2
3
4
5
6
7interface Person {
name: string
}
interface Person {
age: number
}
// 此时Person同时具有name和age属性type可以声明基本数据类型别名/联合类型/元组等,而interface不行
1
2
3
4
5
6// 基本类型别名
type UserName = string;
type UserName = string | number;
// 联合类型
type Animal = Pig | Dog | Cat;
type List = [string, boolean, number];
泛型
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
举个例子,比如我们现在有个这样的需求,我们要实现一个这样的函数,函数的参数可以是任何值,返回值就是将参数原样返回,并且参数的类型是 string,函数返回类型就为 string?
你很容易写下:1
2
3function getValue(arg:string):string {
return arg;
}
现在需求有变,需要返回一个 number
类型的值,你会说,联合类型就完事了:1
2
3function getValue(arg:string | number):string | number {
return arg;
}
但是这样又有一个问题,就是如果我们需要返回一个 boolean 类型,string 数组甚至任意类型呢,难道有多少个就写多少个联合类型?
是的,我们直接用 any
就行了!1
2
3function getValue(arg:any):any {
return arg;
}
尽管 any 大法好,很多时候 any 也确实能够解决不少问题,但是这样也不符合我们的需求了,传入和返回都是 any 类型,传入和返回并没有统一。
这个时候就要祭出我们的泛型了。
基本使用
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
上面的需求,我们如果用泛型来解决的话:1
2
3function getValue<T>(arg:T):T {
return arg;
}
泛型的语法是尖括号 <> 里面写类型参数,一般用 T 来表示第一个类型变量名称,其实它可以用任何有效名称来代替,比如我们用NIUBI也是编译正常的
泛型就像一个占位符一个变量,在使用的时候我们可以将定义好的类型像参数一样传入,原封不动的输出。
使用
我们有两种方式来使用:
定义要使用的类型,比如:
1
getValue<string>('树哥'); // 定义 T 为 string 类型
利用 typescript 的类型推断,比如:
1
getValue('树哥') // 自动推导类型为 string
多个参数
其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U1
2
3
4
5
6function getValue<T, U>(arg:[T,U]):[T,U] {
return arg;
}
// 使用
const str = getValue(['树哥', 18]);
ts 自动识别以上代码为:
function getValue<string, number>(arg: [string, number]): [string, number]
typescript 给我们自动推断出输入、返回的类型
泛型约束
因为泛型 T 不一定包含属性 length,那么我想 getLength 这个函数只允许传入包含 length 属性的变量,该怎么做呢
这时,我们可以使用 extends
关键字来对泛型进行约束1
2
3
4
5
6
7
8interface Lengthwise {
length: number;
}
function getLength<T extends Lengthwise>(arg:T):T {
console.log(arg.length);
return arg;
}
使用:
1 | const str = getLength('树哥') |
这里可以看出,不管你是 str,arr 还是obj,只要具有 length 属性,都可以
泛型接口
1 | interface KeyValue<T,U> { |
泛型类
1 | class Test<T> { |
泛型类型别名
1 | type Cart<T> = { list: T[] } | T[]; |
泛型参数的默认类型
我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。有点 js 里函数默认参数的意思。
1 | function createArray<T = string>(length: number, value: T): Array<T> { |
泛型工具类型
- typeof
关键词除了做类型保护,还可以从实现推出类型
1 | //先定义变量,再定义类型 |
- keyof
可以用来获取一个对象接口中的所有 key 值
1 | interface Person { |
- in
用来遍历枚举类型:
1 | type Keys = "a" | "b" | "c" |
- infer
在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。
1 | type ReturnType<T> = T extends ( |
- extends
有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends
关键字添加泛型约束。
1 | interface Lengthwise { |
现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:1
loggingIdentity(3); // Error, number doesn't have a .length property
当我们传入合法的类型的值,即包含 length 属性的值时:1
loggingIdentity({length: 10, name: '张麻子'}); // 编译正确
- 索引访问操作符
使用 [] 操作符可以进行索引访问:1
2
3
4
5
6interface Person {
name: string;
age: number;
}
type x = Person["name"]; // x is string
内置工具类型
- Required
将类型的属性变成必选
1 | interface Person { |
- Partial
与 Required 相反,将所有属性转换为可选属性1
2
3
4
5
6
7interface Person {
name: string,
age: number,
}
const shuge:Person = {
name:'树哥'
} // error Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.
从上面知道,如果必传而我们少穿传了的话,就会报错
我们使用 Partial
将其变为可选
1 | type User = Partial<Person> |
- Exclude
Exclude<T, U> 的作用是将某个类型中属于另一个的类型移除掉,剩余的属性构成新的类型
1 | type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c" |
- Extract
和 Exclude 相反,Extract<T,U> 从 T 中提取出 U。
1 | type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a" |
- Readonly
把数组或对象的所有属性值转换为只读的,这就意味着这些属性不能被重新赋值。
1 | interface Person { |
- Record
Record<K extends keyof any, T>
的作用是将 K 中所有的属性的值转化为 T 类型。
1 | type Property = 'key1'|'key2' |
- Pick
从某个类型中挑出一些属性出来
1 | type Person = { |
- Omit
与Pick相反,Omit<T,K> 从T中取出除去K的其他所有属性
1 | interface Person { |
- NonNullable
去除类型中的 null 和 undefined
1 | type P1 = NonNullable<string | number | undefined>; // string | number |
- ReturnType
用来得到一个函数的返回值类型
1 | type Func = (value: string) => string; |
- Parameters
用于获得函数的参数类型所组成的元组类型。
1 | type P1 = Parameters<(a: number, b: string) => void>; // [number, string] |
- InstanceType
返回构造函数类型T的实例类型
1 | class C { |
tsconfig.json
在文章开头环境安装部分,记得我们有生成一个 tsconfig.json 文件,那么这个文件究竟有什么用呢
tsconfig.json 是 TypeScript 项目的配置文件。
tsconfig.json 包含 TypeScript 编译的相关配置,通过更改编译配置项,我们可以让 TypeScript 编译出 ES6、ES5、node 的代码。
重要字段
- files - 设置要编译的文件的名称;
- include - 设置需要进行编译的文件,支持路径模式匹配;
- exclude - 设置无需进行编译的文件,支持路径模式匹配;
- compilerOptions - 设置与编译流程相关的选项。
compilerOptions 选项
1 | { |
补充
构造函数
1 | //ts构造函数 |
继承
设置一个父级class用来继承
1 | (function () { |
super
当子类需要添加需求时,用super把父组件的数据继承过来,不然容易造成覆盖
1 |
|
抽象
abstract:
以abstract开头的类是抽象类, 抽象类和其他类区别不大,只是不能用来创建对象,抽象类就是专门用来被继承的类, 抽象类中可以添加抽象方法(但有一个坑就是ts他只会提醒你,还是会被浏览器识别的,我们用ts就是为了规范代码,添加这个数据可以提醒你这个类是父类,不能修改使用的)
1 | (function () { |