都不好意跟别人说自己写了两年的ts,就只会用any,任何数据都是用any搞定,any类型会使编译器放弃检验,等于自废武功,这是个非常不好的习惯,可惜在错误的道路上越走越远,现在难得有机会好好学习一下,总结最近学习ts心得,最重要的一点还是执行力,什么高级特性,只要你码起来,都不在话下,但就是从认知到敲代码中间差了几万八千里,我就是差了2年700多天(哇咔咔 这么惨),

学习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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// 以下所有案例基于此
interface User {
id: number;
age: number;
name: string;
};


// Partial<T> T类型所有子集属性变成可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
type PartialUser = Partial<User>
// 相当于:
type PartialUser = { id?: number; age?: number; name?: string; }


// Pick<T, K> 从T中挑选部分属性Keys构建一个新类型 Keys从属于T的子集
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type PickUser = Pick<User, 'age' | 'name'>
// 相当于
type PickUser = { age: number, name: string }


// Records<K, T> 属性名类型为K, 属性值为T类型,用来将某个类型的属性映射到另一个类型上
// K中所有属性值转化为T类型,申明一个普通的Object对象
type Records<K extends keyof any, T> = {
[P in K]: T
}
type NameKeys = 'cpp' | 'wmh'
const Persopn: Record<NameKeys, User> = {
'cpp': {
name: 'cpp',
age: 30
},
'wmh': {
name: 'wmh',
age: 23
}
}

// Exclude<T, U> 去除T类型中跟U类型的交集部分,返回T中剩下的子集 构建一个新类型
// 取差值 跟Extract类型相反
type Exclude<T, U> = T extends U ? never : T
interface OtherUser {
name: string,
sex: string
}
type ExcludeUser1 = Exclude<'a' | 'b' | 'c', 'a' | 'b'> // 'c'
type ExcludeUser2 = Exclude<keyof User, keyof OtherUser> // 'id' 和 ‘age’


// Extract<T, U> 去俩个类型的交集
type Extract<T, U> = T extends U ? T : never;
interface OtherUser {
name: string,
sex: string
}
type ExtractUser1 = Extract<'a' | 'b' | 'c', 'a' | 'b'> // 'a' | 'b'
type ExtractUser2 = Extract<keyof User, keyof OtherUser> // 'name’


// Omit<T, K> 从类型T中获取所有属性,然后从中剔除Keys属性后构造一个新类型
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
type OmitName = Omit<User, 'name'>
// 相当于
type OmitName = {age: number, id: number }


// Readonly<T> 所有属性设置为readonly 不能再次赋值
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
const readonlyUser: Readonly<Pick<User, 'name'>> = {
name: 'cpp'
}
readonlyUser.name = 'wmh' // error 只读属性不能分配


// Required<T> T类型中所有的属性变为必选项
type Required<T> = {
[P in keyof T] -? : T[p]
}
const testUser: Partial<User> = {
id: 111 // ok
}
const testUser2: Required<User> = {
id: 121 // erroe 缺少 age | name
}
// -? 可选属性前面加- 代表必选

基本类型

重点介绍使用场景和代码实例

enum

  • TypeScript支持数字的和基于字符串的枚举
  • 反向映射: 对于 number 类型的枚举值, 会产生一个对应下标的索引, 如下: up: 1, 1: ‘up’
1
2
3
4
5
6
7
enum Direction {
Up = 1,
Down,
Left,
Right
}
console.log(Dirction) // { 1: "Up", 2: "Down", 3: "Left", 4: "Right", Up: 1, Down: 2, Left: 3, Right: 4 }

unknown

unknown指的是不可预先定义的类型,在很多场景下,它可以替代any的功能同时保留静态检查的能力。
unknown的一个使用场景是,避免使用any作为函数的参数类型而导致的静态类型检查bug

1
2
3
4
5
6
function test(input: unknown): number {
if (Array.isArray(input)) {
return input.length; // Pass: 这个代码块中,类型守卫已经将input识别为array类型
}
return input.length; // Error: 这里的input还是unknown类型,静态检查报错。如果入参是any,则会放弃检查直接成功,带来报错风险
}

可索引的类型

可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型

1
2
3
4
5
interface StringArray {
[index: number]: string
}
let myArr: StringArray = ['cpp', 'chendap']
let myArr2: StringArray = [1, '33'] // error 不能将类型“number”分配给类型“string”。

TypeScript支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用”100”(一个string)去索引,因此两者需要保持一致。
数字索引的返回值必须是字符串索引返回值类型的子类型 这句话很拗口,我是这么理解的,字符串索引的返回值类型必须和其他属性返回值类型相匹配,比如定义的字符串索引返回值类型是string[index: string]: string,其他属性类型也得是string,下面的例子很好说明了这一点:

1
2
3
4
5
6
7
8
9
10
let numb: StringDirctionary = { 
'2': 'ccc',
name: '111',
length: '111',
}
interface StringDirctionary {
[index: string]: string, // [index: number]: string 也是ok, [PropName: string]: string也是ok
length: string, // ok length也是string
name: number // error 类型“number”的属性“name”不能赋给字符串索引类型“string” 索引签名类型都是string,不能将新增的name属性number类型兼容
}

void

never

类型推断

在使用ts时,会遇到下面这种代码

1
2
let age: number = 20
let language: string = 'js'

上面的写法太啰嗦,ts有一个类型推断机制,也就是说ts会根据变量赋的值自动给该变量设置一个类型,我们用更简洁的语法改写下

1
2
let age = 20
let language = 'js'

那什么时候需要给变量设置类型呢?
如果声明了一个变量但没有设置其初始值,推荐设置一个类型,如

1
2
3
let myLove: string
let langs = ['ts', 'js']
myLove = langs[0]

运算符

非空断言运算符 !

用在变量名和函数名之后,某些场景下 你比程序还肯定,这段代码不会有null | undefined类型,

1
2
3
4

function onClick(cb?: () => void) {
cb!() // ts不报错
}

可选链运算符 ?.

?. 用来判断左侧表达式是否是NUll | undefined ,如果是则停止运行程序,减少大量的 && 运算

1
2
3
4
5
6
7
8
9
if (
a &&
Reflect.has(a, 'b') &&
a.b
) {
// before
}

if (a?.b) {} // now

比如我们写出a?.b时,编译器会自动生成如下代码

1
a === null || a === void 0 ? void 0 : a.b;

空值合并运算符 ??

??与||的功能是相似的,区别在于 ?? 在左侧表达式结果为null或者undefined时,才会返回右侧表达式
const b = a ?? 10相当于下列

1
const b =  a !== null && a !== void 0 ? a : 10;

操作符

键值获取 keyof

keyof可以获取一个类型所有键值,返回一个联合类型,如下

1
2
3
4
5
6
type User1 =  {
id: number;
age: number;
name: string;
};
type Userq = keyof User1

keyof的一个典型用途是限制访问对象的key合法化,因为any做索引是不被接受的

1
2
3
function getName<T extends Object, K extends keyof User>(user: T, name: K): T[K]{
return user[name]
}

实例类型获取 typeof 与typeof类型保护

遍历属性 in

in 只能用在

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
interface User {
name: string;
occupation: string;
}
interface Admin {
name: string;
role: string;
}
export type Person = User | Admin;

export const persons: Person[] = [
{
name: 'Max Mustermann',
occupation: 'Chimney sweep'
},
{
name: 'Jane Doe',
role: 'Administrator'
},
];

export function logPerson(person: Person) {
let additionalInformation: string;
if ( 'role' in person) {
additionalInformation = person.role;
} else {
additionalInformation = person.occupation;
}
console.log(` - ${person.name}, ${person.age}, ${additionalInformation}`);
}
persons.forEach(logPerson);

泛型

泛型推断 infer

infer的中文是“推断”的意思,一般是搭配上面的泛型条件语句使用的,所谓推断,就是你不用预先指定在泛型列表中,在运行时会自动判断,不过你得先预定义好整体的结构。举个例子

1
type Foo<T> = T extends {t: infer Test} ? Test: string

首选看extends后面的内容,{t: infer Test}可以看成是一个包含t属性的类型定义,这个t属性的value类型通过infer进行推断后会赋值给Test类型,如果泛型实际参数符合{t: infer Test}的定义那么返回的就是Test类型,否则默认给缺省的string类型。
举个例子

1
2
3
type One = Foo<number>  // string,因为number不是一个包含t的对象类型
type Two = Foo<{t: boolean}> // boolean,因为泛型参数匹配上了,使用了infer对应的type
type Three = Foo<{a: number, t: () => void}> // () => void,泛型定义是参数的子集,同样适配

高级类型

内置类型

1
2
3
4
5
6
7
let b2: Boolean = new Boolean(1)

const body: HTMLElement = document.body;
const divList: NodeList = document.querySelectorAll('div');
document.addEventListener('click', (e: MouseEvent) => {
// do something
});

TS核心库

装饰器支持

开启对装饰器的支持,命令行 编译文件时:
** tsc --target ES5 --experimentalDecorators test.ts**

配置文件

1
2
3
4
5
6
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}

TS对js文件编译时检查

在js中使用类型和错误检测也是很不错的
ts提供了一个特殊功能,允许我们在编译时对代码进行错误检测和类型检测,前提是需要在本地全局安装Typescript,使用时,只需要在js文件第一行加上
// @ts-check 即可,如

1
2
3
4
5
6
7
// @ts-check
/**
* @param {any[]} arr
*/
export function flat(arr) {
return arr.reduce((pre, cur) => pre.concat(Array.isArray(cur) ? flat(cur) : cur), [])
}

参考