TypeScript 初体验
什么是 TypeScript
TypeScript 是静态类型的 JavaScript 超集
类型系统按照「类型检查的时机」来分类,可以分为动态类型和静态类型。
动态类型是指在运行时才会进行类型检查,这种语言的类型错误往往会导致运行时错误。
TypeScript 在运行前需要先编译为 JavaScript,而在编译阶段就会进行类型检查,所以 TypeScript 是静态类型。
TypeScript 是弱类型的 JavaScript 超集
类型系统按照来分类,可以分为强类型和弱类型。
TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性,所以它们都是弱类型。
但可以借助 TypeScript 提供的类型系统,以及 ES-Lint 提供的代码检查功能,来限制变量必须为某一种类型。
也就是说没有绝对的强弱类型之分,依据使用的情况不同,提现不同的类型偏向。
作为对比,Python 是强类型。
TypeScript 的特点
主要特点
- 「省心的类型推断」TS 根据变量初始赋值,推导出的值的类型,大部分类型都不需要手动声明了。
- 「灵活的类型标准」通过在
tsconfig.json
文件中自定义编译选项,从而降低或增加类型检查的标准。 - 「优秀的的 IDE 能力」TS 增强了编辑器代码补全、接口提示、跳转到定义、代码重构等功能。
- 「愈加完善的的生态」对于一些不支持 TS 的第三方库,可以通过安装社区维护的「类型声明库」来实现该库的代码补全、接口提示等能力。
项目迁移
TS 可以和 JS 共存。这就意味着能够把旧的 JS 代码逐步迁移成 TS 代码。对于哪些迁移成本过高的 JS 代码,可以通过「类型声明文件」实现旧项目的渐进式迁移。
启用自动编译
先配置好tsconfig.json
。打开命令行菜单,选择运行任务,选择tsc:watch-tsconfig.json
监听 TS 文件更改。
注意事项
TS 编译阶段仅在语法层面进行类型检查,如果是要在程序运行阶段也需要类型限制,那就要手动进行类型检查,比如判断运行时请求接口拉取的数据类型。
即使在编译阶段报错,TS 仍然能够编译出 JS 文件。如果需要禁止报错的 TS 文件编译成 JS 文件,可以在tsconfig.json
中配置 noEmitOnError
即可。
基础类型
空值 void
在 JS 中没有void
空值的概念,在 TypeScript 中,可以用 void
表示没有任何返回值的函数:
function alertName(): void {
alert('My name is Tom');
}
声明一个 void
类型的变量没有什么用,它只能被赋值为undefined
, null
。
let name: void = undefined;
Null 和 Undefined
与 void
的区别是,undefined
和 null
是所有类型的子类型。也就是说 undefined
类型的变量,可以赋值给其他类型的变量。而 void
类型的变量不能赋值给其他类型的变量。
任意类型
当不知道数据类型时可以通过any
类型对变量进行类型设置。比如不知道请求拉取服务器的数据类型时,就可以指定any
类型完成数据的缓存。但更建议手动进行类型检查。
import { getList } from '@/api/goods'
getList()
.then(res => {
let result: any;
if (res.code === 200) {
result = res.data;
}
else {
throw new Error(res.msg);
}
})
可以认为声明一个any
类型的变量就相当于用原生 JS 进行编程了,不再对变量指定一个基本或者引用类型,程序运行的过程中也能修改变量的值。
需要注意的是,变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型,这就是「类型推断」:
let something: any;// 等价于 let something;
something = 'seven';
something = 7;
something.setName('Tom');
联合类型
联合类型是指,变量、形参、返回值可以是两种或两种以上的类型。通过|
分隔,类似于 JS 中的或运算符:
let num: string | number;
num = 'seven';
num = 7;
myFavoriteNumber = true;// 报错,不属于指定类型中的一种
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
function getStr(think: string | number | boolean): string {
return think.toString();
}
联合类型的变量在被赋值的时候,会根据类型推断的规则推断出一个类型:
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';// 类型推断 string
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;// 类型推断为 number
console.log(myFavoriteNumber.length); // 编译时报错
接口对象声明
在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
换句话说,通过设置接口对象类型,来限制(约束)变量接收对象的形状,也能迅速描述出该接口的形状。
举一个例子:
interface Cat {
name: string,
color: string
}
// 如果多写或少写【未定义的属性】都会类型检查报错,变量和接口的【形状】必须一致。
let miaomi: Cat = {
name: '大黄',
color: '狸花'
}
接口可选属性:
interface Cat {
name: string,
color?: string
}
// 忽略可选属性
let miaomi: Cat = {
name: '大黄',
}
// 添加未定义属性,类型检查会报错
let miaomi: Cat = {
name: '大黄',
gender: 'male'
}
接口任意属性:
// 通过 [propName: string]: any 添加任意类型的属性
interface Cat {
name: string,
color?: string,
[propName: string]: any// 类型也可以指定非 any
}
let miaomi: Cat = {
name: '大黄',
gender: 'male',
age: 1
}
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集。
一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
interface Cat {
name: string,
color?: string,
[propName: string]: string | number
}
let miaomi: Cat = {
name: '大黄',
gender: 'male',
age: 1
}
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly
定义只读属性:
interface Cat {
readonly id: number,
name: string,
color?: string,
[propName: string]: string | number
}
let miaomi: Cat = {
id: 001,
name: '大黄',
gender: 'male',
age: 1
}
miaomi.id = 002;// 只读属性,不可重新赋值
数组类型
普通声明:
let arr1: number[];// 普通声明, 中括号前指定数组内元素的类型
let arr2: any[];// 数组元素为任意类型
arr1 = null;
arr1 = undefined;
arr1 = Array.from(new Set([1, 2, 3, 3, 3]));
泛型声明:
let arr2: Array<object> = [{}];// 泛型声明, 尖括号内指定数组内元素的类型
arr1 = null;
arr1 = undefined;
接口数组声明:
interface IMyArr {
[index: number]: number;// 元素类型也可以是任意类型和联合类型
}
let arr: IMyArr = [1, 2, 3]
不常用,用于表示类数组适用。
函数类型
接口声明函数类型:
interface IMyFunc {
(a: string, b: string): boolean
}
let isContain: IMyFunc = function(a: string, b: string): boolean {
return a.indexOf(b) >= 0;
};
let result: boolean = isContain('123', '1');
console.log(result);
命名声明函数类型:
function isContain(a:string, b:string): boolean {
return a.indexOf(b) >= 0;
}
let result: boolean = isContain('123', '1');
console.log(result);
表达式声明函数类型(过于繁琐,不建议):
const isContain: (a:string, b:string) => boolean = function (a:string, b:string): boolean {
return a.indexOf(b) >= 0;
}
let result: boolean = isContain('123', '1');
console.log(result);
可选参数和默认参数:
用?
添加到参数名前表示可选参数。
// 默认参数不传就是 undefined
function sum(a: number, b: number, c?: number): number {
return a+b+c
}
sum(1, 2);
必须注意,必选参数不能写在可选参数后面。因为必选参数写在可选参数后面的话,仅两个参数时无法去判断第二个参数是必选参数还是可选参数。
函数重载:
当函数需要根据不同的参数类型(联合类型或者any类型),进行不同的运算或者操作时,就可以利用重复函数名但设置不同参数类型启用【函数重载】。
function add(a: number, b: number): number {
return a+b
}
function add(a: string, b: string): string {
return a+b
}
add(1, 2)
add('张', '三')
TS 函数重载就是不同类型约束版本的同一个函数,根据输入类型判断适用哪个函数进行重载。
剩余参数:
在 TS 中使用剩余参数也需要为剩余参数指定类型,比如...args: string[]
就是指定所有剩余参数必须是字符串。
function printArgs(a: number, b: number, ...args: string[]): void {
console.log(args);
}
printArgs(1, 2, 'a', 'b', 'c');
类型断言:
在还不确定类型的时候就访问其中一个类型特有的属性或方法,用【类型断言】指定一个值的类型。然后根据确定的参数类型进行判断或者其他操作。
function showToast(toast: string | boolean = false): void {
if ((toast as string).length) {
console.log('提示信息' + toast);
}
else if ((<boolean>toast).toString == 'true') {
console.log('已开启提示信息');
}
}
由于各种原因,在 TS 中断言最好用值 as 类型
的形式表示
断言为any
类型可以访问任意类型的属性和方法。