TS

正经人谁学TS啊

发展历史

  • 2012-10:微软发布了TypeScript第-个版本(0.8)
  • 2014-10:Angular2发布了基于TypeScript的2.0版本
  • 2015-04:微软发布了Visual Studio Code
  • 2016-05:@types/react?发布,TypeScript可开发React
  • 2020-09:ue发布了3.0版本,官方支持TypeScript
  • 2021-11:v4.5版本发布

为什么TS

和JS的比较

JS TS
动态类型 静态类型
弱类型语言 弱类型语言

动态类型

在执行阶段才确定类型的匹配。编译在执行时

静态类型

提前确定类型匹配。编译在执行前

弱类型语言

数据类型可以被忽略的语言=>一个变量可以赋不同的数据类型的值。

强类型语言

强制数据类型定义的语言=>一个变量被指定了某个数据类型,如果不经过强制转换,永远是这个类型=>一个类型的变量不会被当成另一种类型处理,(强类型定义语言是类型安全的语言)

TS优势

静态类型
  • 可读性增强:基于语法解析TSDoc,ide增强
  • 可维护性增强:在编译阶段暴露大部分错误=>多人合作的大型项目中获得更好的稳定性和开发效率
JS的超集
  • 包含于兼容所有的JS特性,支持共存
  • 支持渐进式引入的升级

基本语法

基础数据类型

JS

1
2
3
4
5
const q = 'string';
const w = 1;
const e = true;
const r = null;
const t = undefined;

TS

1
2
3
4
5
const q: string = 'string';
const w: number = 1;
const e: boolean = true;
const r: null = null;
ocnst t: undefined = undefined;

对象类型

TS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const bytedancer: IBytedancer = {
jobId: 9303245,
name: 'Lin',
sex: 'man',
age: 28,
hobby: swimming',
}

/*自定义对象类型:I开头表示是个类型,与普通对象/类的作区分*/
interface IBytedancer {
/*只读属性:约束属性不可在对象初始化外赋值*/
readonly jobId: number;

name: string;
sex: 'man'|'woman'|'other';
age: number;

/*可选属性:定义该属性可以不存在*/
hobby?: string;

/*任意属性:约束所有对象属性都必须是该属性的子类型*/
[key: string]: any;
}


/*报错:无法分配到"jobId”,因为它是只读属性*/
bytedancer.jobId = 12345;
/*成功:任意属性标注下可以添加任意属性*/
bytedancer.plateform = 'data';
/*报错:缺少属性"name",hobby可缺省*/
const bytedancer2: IBytedancer = {
jobId: 89757,
sex: 'woman',
age: 18,
}

函数类型

JS

1
2
3
4
function add(x, y) {
return x + y;
}
const mult = (x, y) => x * y;

TS

1
2
3
4
5
6
7
8
9
function add(x: number, y: number): number {
return x + y;
}
const mult: (x: number, y: number) => number = (x, y) => x * y;
↓更加清晰
interface IMult {
(x: number, y: number): number;
}
const mult: IMult = (x, y) => x * y;

函数重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*对getDate函数进行重载,timestamp为可缺省参数*/
function getDate(type:'string',timestamp?:string):string;

interface IGetDate {
(type:'string',timestamp?:string):string;
(type:'date',timestamp?:string):Date;
(type:'string'|'date',timestamp?:string):Date string;
}

/* 报错,因为没有给参数声明类型 */
/* 不能将类型“(type:any,timestamp:any)=>string|Date"分配给类型“IGetDate”。
不能将类型“string|Date"分配给类型“string”。
不能将类型“Date”分配给类型“string”。ts(322) */
const getDate2:IGetDate = (type, timestamp) => {
const date = new Date(timestamp);
return type ==='string' ? date.toLocalestring():date;
}
也就是说,函数里面的函数的类型范围要小于函数本身所在的类型范围。

数组类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*「类型+方括号」表示-常用*/
type IArr1 = number[];
type IArr = string[];
/*泛型表示-常用,第三个是对象的简写形式*/
type IArr2 = Array<string | number | Record<string,number>>;

/*元祖表示*/
type IArr3 = [number, number, string, string];
/*接口表示*/
interface IArr4 {
[key: number]: any;
}

const arr1: IArr1=[1,2,3,4,5,6];
const arr2: IArr2=[1,2,'3','4',{a:1}];
const arr3: IArr3=[1,2,'3','4'];
const arr4: IArr4 ['string',() => null, {}, []];

TS补充类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*空类型,表示无赋值*/
type IEmptyFunction = () => void;
/*任意类型,是所有类型的子类型*/
type IAnyType = any;
/*枚举类型:支持枚举值到枚举名的正、反向映射*/
enum EnumExample {
add = '+',
mult = '*',
}
EnumExample['add'] === '+';
EnumExample['*'] === 'mult';

enum ECorlor { Mon, Tue, Wed, Thu, Fri, Sat, Sun };
ECorlor['Mon'] === 0;
ECorlor[0] === 'Mon';
/* 泛型 */
type INumArr = Array<number>;

TS泛型

特征:不预先指定类型的时候,也就是在使用某类型时,才确定该类型。

使用

1
2
3
4
5
6
7
8
9
function getRepeatArr(target) {
return new Array(100).fill(target);
}

type IGetRepeatArr = (target: any) => any[];

/* 不预先指定具体的类型,而在使用的时候再指定类型的一种特性 */
/* 函数中泛型:<T>函数定义 */
type IGetRepeatArrR = <T>(target: T) => T[];
1
2
3
4
5
6
7
8
9
10
11
12
13
/* 类,泛型接口,Object中:类型名<T> */

/* 泛型接口 & 多泛型 */
interface IX<T,U> {
key: T;
val: U;
}
/* 泛型类 */
class IMan<T> {
instance: T;
}
/* 泛型别名 */
type ITypeArr<T> = Array<T>;

有点高级的语法

1
2
3
4
5
6
函数的泛型
/* 泛型约束:限制泛型必须符合字符串 */
type IGetRepeatStringArr = <T extends string>(target: T) => T[];
const getstrArr: IGetRepeatStringArr = target => new Array(100).fill(target);
/*报错:类型“number"的参数不能赋给类型“string”的参数*/
getstrArr(123);
1
2
3
4
5
6
类型别名的泛型
/*泛型参数默认类型*/
type IGetRepeatArr<T = number>(target: T) => T[];
const getRepeatArr: IGetRepeatArr target = new Array(100).fill(target);
/*报错:类型“string”的参数不能赋给类型“number"的参数*/
getRepeatArr('123')

类型别名&类型断言

这段代码是使用TypeScript定义了一个函数”keyBy”,它用于将一个对象数组转换为一个键值对字典。

第1行:通过”type”关键字定义了一个别名类型”IObjArr”,它代表了一个对象数组,每个元素都是一个包含key属性与任意其他属性的对象。

第3行:声明了一个名为”keyBy”的函数,它参数为”objArr”,它类型为Array,T继承自IObjArr,意味着objArr的元素必须是包含key属性的对象。

第5行:定义了一个名为result的变量,初始值是将objArr使用reduce方法转换为的对象,reduce方法会接收一个回调函数,对数组进行遍历,不断累积最终的结果。

第9行:使用”as”关键字断言result的类型为Record<string,T>,意味着result是一个以字符串为键,T类型为值的字典。

最后,返回result。

1
2
3
4
5
6
7
8
9
10
11
12
13
/*通过type关键字定义了I0 bjArrl的别名类型*/
type IObjArr = Array<{
key: string;
[objKey: string]: any;
}>
function keyBy<T extends IObjArr>(objArr: Array<T>){
/*未指定类型时,result类型为{}*/
const result = objArr.reduce((res, val, key) => {
res[key] = val;
return res;
},{});
/*通过as关键字,断言result类型为正确类型*/
return result as Record<string,T>;

字符串/数字 字面量

1
2
3
4
5
6
7
/*允许指定字符串/数字必须的固定值*/

/*IDomTag必须为html、body、div、span中的其一*/
type IDomTag = 'html' | 'body' | 'div' | 'span';

/*I0 ddNumber必须为1、3、5、7、9中的其一*/
type IOddNumber = 1 |3 | 5 | 7 | 9;

高级类型

来自chatGPT

一些常用类型:

  1. 可读属性
  2. 可选属性
  3. 索引签名
  4. 字面量类型

常用的 TypeScript 高级类型有:

  1. 交叉类型 (Intersection Types)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    interface User {
    id: number;
    name: string;
    }

    interface Role {
    role: string;
    }

    type UserWithRole = User & Role;

    const userWithRole: UserWithRole = {
    id: 1,
    name: 'John Doe',
    role: 'admin'
    };
  2. 联合类型 (Union Types)

    1
    2
    3
    4
    5
    6
    7
    type UserID = number | string;

    const userID: UserID = 123;
    const anotherUserID: UserID = 'abc';

    // 错误,因为true不是number或string
    const wrongUserID: UserID = true;
  3. 类型别名 (Type Aliases)

  4. 映射类型 (Mapped Types)

  5. 条件类型 (Conditional Types)

  6. 可辨识联合类型 (Discriminated Unions)

  7. 类型推断 (Type Inference)

  8. 类型保护 (Type Guards)

  9. 索引类型 (Index Types)

  10. 联合类型推断 (Union Type Inference)

1.联合/交叉类型

为书籍列表编写类型

1
2
3
4
5
6
7
8
9
const bookList = [{
author: 'xioaming',
type: 'history',
range: '2001-2021'
},{
author: 'xiaoli',
type: 'story',
theme: 'love',
}]

↓类型声明繁琐,存在较多重复

1
2
3
4
5
6
7
8
9
10
11
interface IHistoryBook {
author: string;
type: string;
range: string;
}
interface IStoryBook {
author: string;
type: string;
theme: string;
}
type IBookList = Array<IHistoryBook | IStoryBook>;

改进后

  • 联合类型:IA | IB 联合类型表示一个值可以是几种类型之一
  • 交叉类型:IA & IB 多种类型叠加到一起形成的一种类型,包含了所有所需的所有类型特性
1
2
3
4
5
6
7
8
9
type IBookList = Array<{
author: string;
} & ({
type: 'history';
range: string;
} | {
type: 'story';
theme: string;
})>

2.类型保护&类型守卫

原型

1
2
3
4
5
6
7
8
9
10
11
12
13
interface IA {a: 1, a2: 2}
interface IB {b: 1, b1: 2}

function log(args: IA | IB) {
/*报错:类型“IA|IB”上不存在属性“a”。类型“IB”上不存在属性“a”。*/
/*结论:访问联合类型时,处于程序安全,仅能访问联合类型中的交集部分*/
if(arg.a) {
console.log(arg.a1);
}
else {
console.log(arg.b1);
}
}

改造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface IA a:1,al:2
interface IB b:1,b1:2

/*类型守卫:定义一个函数,它的返回值是一个类型谓词,生效范围为子作用域*/
function getIsIA(arg: IA | IB): arg is IA {
/*as-断言,当存在a时,断言为IA类型*/
return !!(arg as IA).a;
}

function log2(arg: IA | IB) {
if (getIsIA(arg)){
console.log(arg.a1)
}
else {
console.log(arg.b1);
}
}

实现函数logBook类型

1
2
3
4
5
6
7
8
9
10
//函数接受书本类型,并logger出相关特征
function logBook(book: IBookItem) {
// 联合类型 + 类型保护 = 自动类型判断
if(book.type === 'history') {
console.log(book.range);
}
else {
console.log(book.theme);
}
}

3.merge函数类型

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
*实现merge函数类型
*要求sourceObj必须为targetObj的子集
*/
function merge1(sourceObj, targetObj){
const result {...sourceobj };
for(let key in targetobj) {
const itemVal = sourceobj[key];
itemVal && (result[key] = itemVal);
}
return result;
}
function merge2(sourceObj, targetObj){
return {...sourceobj,...targetobj };
}

TS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface ISourceobj {
x?: string;
y?: string;
}
/*定义时不明确,使用时才明确*/
interface ITargetobj {
x: string;
y: string;
}
type IMerge = (sourceobj: ISourceobj, targetobj: ITargetobj) => ITargetobj;
/**
*类型实现繁琐:若obj类型较为复杂,则声明source和target便需要大量重复2遍
*容易出错:若target增加/减少key,则需要source联动去除
*/

改进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface IMerge {
<T extends Record<string,any>>(sourceObj: Partial<T>, targetObj: T): T;
}

type IPartial<T extends Record<string, any>> = {
[P in keyof T]?: T[P];
}

type IKeys = keyof { a: string; b: number };

/* 索引类型:
关键字【keyof】,其相当于取值对像中的所有key组成的字符串字面量,如=>type IKeys "a"|"b"
关键字【in】,其相当于取值字符串字面量中的一种可能,配合泛型P,即表示每个key
关键字【?】,通过设定对象可选选项,即可自动推导出子集类型
*/
解释说明

这段代码是一个定义了多个TypeScript类型的代码。

  1. IMerge是一个接口,它定义了一个方法,该方法接受两个对象,并返回一个新的对象,其中第一个对象是Partial类型,第二个对象是T类型,T是Record类型的泛型。

  2. IPartial是一个类型别名,它定义了T类型的可选部分,T是Record类型的泛型。

  3. IKeys是一个类型别名,它定义了一个名为’a’和’b’的对象的键的类型,其中’a’的类型是字符串,’b’的类型是数字。

  4. 关系:

    • IMerge 是一个接口,它定义了一个合并两个对象的方法,并将合并后的对象返回。它接受两个对象作为参数,分别是源对象和目标对象,返回值为目标对象。

    • IPartial 是一个类型别名,它定义了一个可选字段的对象。也就是说,可以有一些字段是不是必填的。

    • IKeys 是一个类型别名,它定义了一个字符串字面量类型的联合类型。它的值是 ‘a’ 和 ‘b’。

4.函数返回值类型

1
2
3
4
5
6
7
8
9
10
11
// 实现函数delayCall的类型声明
// delayCall接受一个函数作为入参,其实现延迟1s运行函数
// 其返回promise,结果为入参函数的返回结果
function delayCall(func) {
return new Promise(resolve => {
setTimeout(() => {
const result = func();
resolve(result)
}, 1000);
});
}

↓怎么写个类型声明嘞?

1
2
3
4
5
6
7
8
9
10
// 定义一个类型别名, extends泛型限定为函数
type IDelayCall = < T extends () => any >(func: T) => ReturnType<T>;

type IReturnType< T extends (...args: any) => any > = T extends (...args: any) => infer R ? R : any; /*类型推断*/

// 关键字【extends】跟随泛型出现时,表示类型推断,其表达可类比三元表达式
// 如 T === 判断类型?类型A:类型B

// 关键字【infer】)出现在类型推荐中,表示定义类型变量,可以用于指代类型
// 如该场景下,将函数的返回值类型作为变量,使用新泛型R表示,使用在类型推荐命中的结果中

解释说明

这段 TypeScript 代码定义了两种类型:IDelayCallIReturnType.

  • IDelayCall是一个泛型函数,接受一个类型为 (...args: any) => any 的参数func,返回值为 func 的返回类型(使用了 TypeScript 的 ReturnType 内置类型)。

  • IReturnType 也是一个泛型函数,它接受一个类型为 (...args: any) => any 的参数,并使用 TypeScript 的 infer关键字来推断该函数的返回类型,如果返回类型不能被推断出来,则默认为any类型。

这两个类型定义之间是有关系的。

  • IDelayCall 类型定义了一个函数,接受一个函数 func 作为参数,并返回该函数的返回类型。

  • IReturnType 类型定义了一个模板类型,它接受一个函数类型为参数 T,并通过 TypeScript 的推导功能将该函数的返回类型推导为 R,最后返回结果为 R 或者 any,具体取决于 T 类型是否满足上述函数类型。

因此,我们可以将 IReturnType 定义为 IDelayCall 函数的内部类型,以便进行更简洁、清晰的代码编写。

工程运用

浏览器Web

webpack

  1. 配置webpack loader相关配置(处理ts文件,转化成js文件)
  2. 配置tsconfig.js文件
  3. 运行webpack启动/打包
  4. loader处理ts文件时,会进行编译与类型检查

相关loader:

  1. awesome-typescript-loader
  2. babel-loader

NodeJs

使用TSC编译

  1. 安装node npm
  2. 使用npm安装tsc
  3. 配置tsocnfig.js文件
  4. 使用tsc运行编译得到的js文件