为什么使用TypeScript
TypeScript对比JavaScript:
- JavaScript
- 动态类型,在执行时才做类型匹配
- 弱类型语言
- TypeScript
- 静态类型,在执行前就进行类型匹配
- 弱类型语言
TypeScript的优点:
- 静态类型
- 可读性增强:基于语法解析TSDoc,IDE增强
- 可维护性增强:可以在编译时暴露大部分错误
- 稳定性和开发效率更高
- JS的超集
- 包含与兼容所有JS特性,支持共存
- 支持渐进式引入和升级
基本语法
基本数据类型
对于JS的数据类型到TS数据类型的对比:
JS代码const 变量名 = 变量值;:
const s = 'string';
const n = 1;
const b = true;
const r = null;
const t = undefined;
TS代码consnt 变量名: 变量类型 = 变量值;:
const s: string = 'string';
const n: number = 1;
const b: boolean = true;
const r: null = null;
const t: undefined = undefined;
对象数据类型
const bytedancer: IBytedancer = {
jobId: 123456,
name: 'Lil',
sex: 'man',
age: 18,
hobby: 'play',
}
interface IBytedancer {
readonly jobId: number; // 只读属性,约束属性不可在对象初始化外赋值
name: string;
sex: 'man' | 'woman' | 'other';
age: number;
hobby?: string; // 可选属性,定义该属性可以缺省
[key: string]: any; // 约束所有对象的属性都必须是该属性的子类型
}
函数类型
为函数添加类型声明:
原始JS代码:
function add(x, y) {
return x + y;
}
const mult = (x, y) => x * y;
修改后的TS代码:
function add(x: number, y: number): number {
return x + y;
}
const mult: (x: number, y: number) => number = (x, y) => x * y;
函数还可以通过interface来定义类型:
interface IMult {
(x: number, y: number): number;
}
const mult: IMult = (x, y) => x * y;
函数重载
function getDate(type: 'string', timestamp?: string): string;
function getDate(type: 'date', timestamp?: string): Date;
function getDate(type: 'string' | 'date', timestamp?: string): Date | string {
const date = new Date(timestamp);
return type === 'string' ? date.toLocaleString() : date;
}
const x = getDate('date'); // x: Date
const y = getDate('string', '2018-11-24'); // y: string
使用接口简化代码:
interface IGetDate {
// 因为匿名函数赋值会进行自动类型判断,判断返回值类型为Date | string,所以接受返回类型要更大
(type: 'date', timestamp?: string): any;
(type: 'string', timestamp?: string): any;
(type: 'date' | 'string', timestamp?: string): Date | string;
}
const getDate2: IGetDate = (type, timestamp) => {
const date = new Date(timestamp);
return type === 'string' ? date.toLocaleString() : date;
}
数组类型
数组是一种特殊的Object,可以有多种定义方式:
// type关键字用于给类型添加别名,方便调用
// 类型 + []表示
type Arr1 = number[];
// 泛型表示,定义可以包含的数据类型
type Arr2 = Array<string | number | Record<string, number>>
// 元组表示,定义每一项的数据类型
type Arr3 = [number, number, string, string];
// 接口表示,定义key的类型及值的类型
interface Arr4 {
[key: number]: any;
}
const arr1: Arr1 = [1, 2, 3, 4, 5];
const arr2: Arr2 = [1, 2, '3', '4', { a: 1 }];
cosnt arr3: Arr3 = [1, 2, '3', '4'];
const arr4: Arr4 = ['string', () => null, {}, []];
TypeScript补充类型
// 空类型,表示无赋值
type IEmptyFunction = () => void; // void一般作为函数的返回值类型,表示没有返回值
// 任意类型,是所有类型的并集
type IAnyType = any;
// 枚举类型,支持枚举值到枚举名的互相映射
enum EnumExample {
add = '+';
mult = '*';
}
EnumExample['add'] === '+';
EnumExample['*'] === 'mult';
// 泛型
type INumArr = Array<number>;
TypeScript泛型
泛型:是指不先指定具体类型,而在使用的时候在指定类型的一种特性。
泛型写在类/函数的名字之后,参数之前。
function getRepeatArr(target) {
return new Array(100).fill(target);
}
type IGetRepeatArr = (target: any) => any[];
// 泛型
type IGetRepeatArr = <T>(target: T) => T[];
泛型使用场景:
// 泛型接口 多泛型
interface IX<T, U> {
key: T;
value: U;
}
// 泛型类
class IMan<T> {
instance: T;
}
// 泛型别名
type ITypeArr<T> = Array<T>
泛型特别语法:
// 泛型约束
type IGetRepeatStringArr = <T extends string>(target: T) => T[]; // 限制泛型必须符合字符串
const getStrArr: IGetRepeatStringArr = target => new Array(100).fill(target);
// 泛型参数默认类型
type IGetRepeatArr<T = number> =(target: T) => T[]; // 设置泛型参数默认类型为number
const getRepeatArr: IGetRepeatArr = target => new Array(100).fill(target);
类型别名 & 类型断言
使用type关键字定义类型别名:
type IObjArr = Arrray<{
key: string;
[objKey: string]: any;
}>
使用as关键字来进行类型断言:
function keyBy<T extends IObjArr>(objArr: Array<t>) {
// 未指定类型时,result类型为{}
const result = objArr.reduce((res, val, key) => {
res[key] = val;
return res;
}, {});
return result as Record<string, T>;
}
字符串/数字 字面量
使用type关键字指定字符串/数字允许的固定值:
type IDomTag = 'html' | 'body' | 'div' | 'span';
type IOddNumber = 1 | 3 | 5 | 7 | 9;
高级类型
联合/交叉类型
联合类型:A | B,表示一个值可以是几种类型之一
交叉类型:A & B,表示多种类型叠加在一起成为一种类型,它包含了所需的所有类型的特性
type IBookList = Array<{
author: string; //公共属性
} & ({ // 非公共属性与公共属性之间使用交叉类型连接
type: 'history';
range: string;
} | { // 非公共属性互相之间使用联合类型连接
type: 'story';
theme: string;
})>
const bookList = [{
author: 'xiaoming',
type: 'hitory',
range: '2001-2021',
}, {
autorh: 'xiaoli',
type: 'story',
theme: 'love',
}]
类型保护和类型守卫
类型保护:访问联合类型时,出于程序安全,仅能访问联合类型中的交集部分。 当需要访问联合类型中的各自属性时,可以使用类型守卫实现。
类型守卫:定义一个函数,其返回值是一个类型谓词,生效范围为子作用域
interface A { a: 1, a1: 2 }
interface B { b: 1, b1: 2 }
// 类型守卫
function getIsA(arg: A | B): arg is A { // arg is A 类型谓词
return !!(arg as A).a; // 返回值为true或false,表示是否为A类型
}
function log(arg: A | B) {
if (getIsA(arg)) {
console.log(arg.a1);
} else {
console.log(arg.b1)
}
}
简化类型保护
使用typeof、instanceof
function reverse(target: string | Array<any>) {
// typeof 类型保护
if (typeof target === 'string') {
return target.split('').reverse.join('');
}
// instanceof 类型保护
if (target instanceof Object) {
return target.reverse();
}
}
使用联合类型 + 类型保护,可以进行自动类型判断,如上述的BookList例子:
function logBook(book: IBookItem) {
// 联合类型 + 类型保护 = 自动类型判断
if (book.type === 'history') {
console.log(book.range);
} else {
console.log(book.theme);
}
}
类型子集表示
普通的子集表示,存在大量重复声明,且容易出错,Son和Father需要联动修改,且不能传入任意Object:
interface Son {
x?: string;
y?: string;
}
interface Father {
x: string;
y: string;
}
可以使用泛型优化子集声明:
interface IMerge {
// Record<string, any>表示任意对象
<T extends Record<string, any>>(son: Partial<T>, father: T): T;
}
type IPartial<T extends Record<string, any>> = {
// keyof 用于获取对象中所有key组成的字面量
// in 用于取值字符串字面量中的一种可能,配合泛型P即可匹配每个key
[P in keyof T]?: T[P]; // T[P]表示T对象中的P属性的类型
}
函数返回值类型
对于延迟函数,传入一个函数,在一定时间后返回该函数的返回值。
函数的返回值类型由传入的函数决定,无法直接确定,所以需要通过泛型表达:
// <T extends () => any> 表示限定泛型类型为函数
type IDelayCall = <T extends () => any>(func: T) => ReturnType<T>;
// 关键字extends跟随泛型时表示类型推断,其写法类似三元表达式
// infer出现在类型推断中时,表示定义类型变量
// => infer R 表示将函数的返回类型定义为类型变量R
type IReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
工程应用
Web
设置WebPack支持TypeScript步骤:
- 配置webapack loader相关配置,将webpack不能识别的文件转为可识别,常用的loader:
- awesome typescript loader
- babel loader
- 配置tsconfig.js文件,约束TypeScript的严格程度
- 运行webpack启动/打包
- loader处理ts文件时,会进行编译与类型检查,转化为js文件
NodeJS
设置NodeJS支持TypeScript,使用TSC编译,将ts文件转换为js文件:
- 安装node与npm
- 配置tsconfig.js文件
- 使用npm安装TSC
- 使用TSC运行编译得到js文件