5 - TypeScript 的概念和语法
一、什么是 TypeScript
1. TypeScript 的历史
- 2012-10 微软发布了 TypeScript 的第一个版本
- Angular、 React 和 Vue 3.0 都官方支持了 TypeScript
- Visual Studio Code 完美兼容 TypeScript
2. 为什么选择 TypeScript
-
TypeScript 是添加了类型系统的 JavaScript
-
TypeScript 是 JavaScript 的超集
-
完全兼容 JavaScript 特性,支持共存
-
支持渐进式引入与升级
-
-
TypeScript 是静态类型语言,可以在编译阶段提供类型检查
- 静态类型的优势
- 增强代码可读性:基于语法解析 TSDoc,IDE 可以提供增强
- 增强代码可维护性:在编译阶段暴露大部分错误,节省 debug 实现
动态类型在执行阶段匹配类型,静态类型在编译阶段匹配类型
关于动静类型和强弱类型的定义可参考:辨析编程语言的四种类型:动静类型与强弱类型
- 静态类型的优势
二、TypeScript 的语法
1. 基础数据类型
TypeScript 作为静态类型语言,其需要在定义变量时指定变量的类型
const q: string = "string"; //字符串
const w: number = 1; //数字
const e: boolean = true; //布尔值
const r: null = null; //null
const t: undefined = undefined; //undefined
使用
<varName>: <varType> = <value>
来声明变量
2. 对象类型
2.1 定义接口
TypeScript 中使用接口(interface)来定义对象的类型和内容
interface IByteDance {
readonly jobId: number;
name: string;
sex: 'man' | 'woman' | 'other';
age: number;
hobby?: string;
[key: string]: any;
}
readonly
:定义该属性为只读,即在对象初始化外之外不可被赋值es
sex: 'man' | 'woman' | 'other'
:定义sex
属性,仅能为该三个选项之一
hobby?
:定义hobby
属性为可选属性,可以不定义该属性
[key: string]: any
:任意属性,并约束所有对象属性都必须是该属性的子类型
any
类型将会跳过编译时类型检查,等到运行时再确定具体的类型
2.2 定义对象
透过将对象类型指向接口来实现对象的类型和内容预定义
const byteDancer: IByteDance = {jobId: 123, name: "Bob", sex: "man", age: 15}
定义
byteDancer
对象的类型为IByteDance
未定义可选属性
hobby
2.3 修改对象
2.3.1 只读属性
byteDancer.jobId = 456;
TS2540: Cannot assign to 'jobId' because it is a read-only property.
只读属性,无法修改
2.3.2 任意属性
byteDance.platform = "data";
任意属性标注下可以添加任意属性
2.3.3 缺少属性
const byteDancer2: IByteDance = {jobId: 123, sex: "man", age: 15}
TS2741: Property 'name' is missing in type '{ age: number; jobId: number; sex: "man"; }' but required in type 'IByteDance'.
缺少属性
name
,bobby
可缺省
3.函数类型
TypeScript 中使用function funcName(): <returnType> { ... }
来定义函数类型
3.1 带参数函数
function mult(x: number, y: number): string {
return (x * y).toString();
}
定义函数
mult
,包含参数x
和y
,均为number
类型,函数的返回值为string
类型
3.2 可选参数
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + " " + lastName;
}
else {
return firstName;
}
}
lastName?
:可选参数lastName
3.3 默认参数
function calculate_discount(price: number, rate: number = 0.50) {
let discount = price * rate;
console.log("Result: ", discount);
return discount;
}
rate: number = 0.50
:定义参数rate
的类型为number
,默认值为0.50
3.4 Lambda 函数(箭头函数)
3.4.1 基于 interface 实现
interface Imult {
(x: number, y: number): string;
}
const mult: Imult = (x, y) => (x * y).toString()
3.4.2 常规实现
const mult: (x: number, y: number) => string = (x, y) => (x * y).toString()
3.5 函数重载
用来实现不同参数输入对应不同参数输出的函数。
需要定义多个重载签名,一个实现签名,一个函数体构造。
let suits = ["hearts", "spades", "clubs", "diamonds"];
// 定义重载签名
function greet(person: string): string;
function greet(persons: string[]): string[];
// 定义实现签名
function greet(person: unknown): unknown {
if (typeof person === 'string') {
return `Hello, ${person}!`;
} else if (Array.isArray(person)) {
return person.map(name => `Hello, ${name}!`);
}
throw new Error('Unable to greet');
}
console.log(greet(suits[0]));
console.log(greet(suits));
参考资料
4. 数组类型
TypeScript 中通常使用类型[]
或Array<>
来表示一个数组
// 类型[] 表示
type IArr1 = number[];
// 泛型表示
type IArr2 = Array<string | number>;
// 元组表示
type IArr3 = [number, number, number, string];
// 接口表示
interface IArr4 {[key: number]: any;}
const arr1: IArr1 = [1, 2, 3]
const arr2: IArr2 = ["Bob", 123, "123"]
const arr3: IArr3 = [123, 123, "abc", "abc"]
const arr4: IArr4 = ["string", () => null, {}, []]
number[]
:由数字组成的数组
Array<string | number>
:使用Array
关键字定义数组,由string
或number
类型组成
[number, number, string, string]
:前两项为数字,后两项为字符串
5. 空类型
空类型,表示无赋值
type IEmptyFunction = () => void;
6. 任意类型
任意类型,所有类型的子类型
type IAnyType = any;
7. 枚举类型
枚举类型,对标准数组类型的补充,支持枚举值到枚举名的正、反向映射,可以手动为元素编号
enum EColor {Mon, Tue, Wed, Thu, Fri, Sat, Sun}
console.log(EColor['Tue']); //1
console.log(EColor[1]); //Tue
这将会把
Mon
自动赋值为0
,Sun
自动赋值为6
enum EColor {Mon = 1, Tue, Wed, Thu, Fri, Sat, Sun}
这将会把
Mon
赋值为1
,Sun
自动赋值为7
enum EColor {Mon = -2, Tue, Wed, Thu, Fri, Sat, Sun}
这将会把
Mon
赋值为-2
,Sun
自动赋值为4
enum EColor {Mon = 2, Tue = 3, Wed = 5, Thu = 8}
console.log(EColor['Tue']); //3
console.log(EColor[5]); //Wed
也可以像这样对每一个元素手动赋值
8. 泛型
8.1 什么是泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
8.2 使用泛型
function createArray<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
createArray(3, 20); // [20, 20, 20]
这个函数被用来创建一个数组,给定两个参数(填充数量,填充内容)
这里我们预期这个函数的
value
参数应该接受一个未知类型T
的值,返回一个仅包含类型为T
的元素的数组
createArray<T>
:定义这个函数将要接受一个未知类型T
的参数
value: T
:定义value
参数的类型为T
Array<T>
:定义元素类型为T
的数组
8.3 多个类型参数
定义泛型的时候,可以一次定义多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
8.4 泛型约束
由于在函数内使用泛型时,不知道其是那种类型,不能随意操作其属性或方法。
这时可以使用interface
关键字定义接口,并使用extends
关键字要求泛型T
继承自Lengthwise
,并基于此要求T
必须包含length
属性
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
T extends Lengthwise
:T
继承自Lengthwise
,即要求T
必须包含length
属性
8.5 泛型参数的默认类型
在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
<T = string>
:定义泛型T
的默认类型为string
9. 类型别名
当需要重复使用一个较为复杂的类型时,可以使用type
关键字来指定类型别名,即使用一个变量来指代一个复杂类型
type IObjArr = Array<{
key: string;
[objKey: string]: any;
}>
10. 类型断言
-
类型断言(Type Assertion)可以用来手动指定一个值的类型
-
一般使用
as
关键字来指定类型断言 -
类型断言的作用主要是告诉编译器如何判断这是什么类型,这并不会体现在生成的JS中
TypeScript 代码:
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
JavaScript 代码:
var someValue = "this is a string";
var strLength = someValue.length;
11 字面量(字符串/数字)
字面量用以指定字符串或数字所必须使用的固定值
type IDomTag = 'html' | 'body' | 'div' | 'span';
type IOddNumber = 1 | 3 | 5 | 7 | 9;
IDomTag
必须为'html'
、'body'
、'div'
、'span'
其中之一
IOddNumber
必须为1
、3
、5
、7
、9
其中之一
三、TypeScript 高级类型
1. 联合类型 & 交叉类型
&
:交叉类型(Intersection Types),将多个类型合并为一个类型,包含被合并的类型的所有属性
|
:联合类型(Union Types),几种值之一,用|
分隔
type IBookItem = {
author: string;
} & ({
type: 'history';
range: string;
} | {
type: 'story';
theme: string;
});
此处定义了
IBookItem
类型,要求必须包含author
属性和type
属性同时利用交叉类型,定义
type
值为'history'
则必须有string
类型的range
属性;定义type
值为'story'
则必须有string
类型的theme
属性。
2. 类型保护 & 类型守卫
类型保护和类型守卫是 TypeScripy 自动类型推断的一种应用
2.1 typeof
关键字
let var1: string | number[];
if (typeof var1 === 'string') {
// var1.length
} else {
// var1.push()
}
此处使用
typeof var1 === 'string'
限定了if
语句的第一个代码块内,var1
的类型必然为string
,则 TypeScript 将会自动为该区域提供适用于string
类型的代码检查而开始处
let var1: string | number[];
规定了var1
只能为string
类型或数组,则 TypeScript 会自动判定else
语句内的var1
类型为数组,并提供相关的代码检查
2.2 in
关键字
in
关键字用来判断一个对象内是否存在一个属性
type type1 = {author: string, name: string, };
let type2: type1 = {author: 'a', name: 'b'};
if ('author' in type2) { ... } else { ... }
'author' in type2
:判断'author'
属性是否存在于对象type2
内
2.3 instanceof
关键字
instanceof
关键字用来判断一个对象是否是某个类的实例
function Person(name) {
this.name = name;
}
const person = new Person("John");
console.log(person instanceof Person); // true
四、TypeScript 工程应用
1. webpack
-
配置 webpack loader 相关配置
转换 webpack 无法识别的文件,例如 ts 转 js
-
配置 tsconfig.js 文件
-
运行 webpack 启动/打包
-
loader 处理 ts 文件时,会进行编译与类型检查
相关 loader
2. Node.js
Node.js 使用 TSC 编译
- 安装 node 和 npm
- 配置 tsconfig.js 文件
- 使用 npm 安装 tsc
- 使用 tsc 运行编译得到的 js 文件