都不好意跟别人说自己写了两年的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 ; }; type Partial<T> = { [P in keyof T]?: T[P]; }; type PartialUser = Partial<User>type PartialUser = { id?: number ; age?: number ; name?: string ; }type Pick<T, K extends keyof T> = { [P in K]: T[P]; }; type PickUser = Pick<User, 'age' | 'name' >type PickUser = { age: number , name: string }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 } } type Exclude<T, U> = T extends U ? never : Tinterface OtherUser { name: string , sex: string } type ExcludeUser1 = Exclude<'a' | 'b' | 'c' , 'a' | 'b' > type ExcludeUser2 = Exclude<keyof User, keyof OtherUser> type Extract<T, U> = T extends U ? T : never;interface OtherUser { name: string , sex: string } type ExtractUser1 = Extract<'a' | 'b' | 'c' , 'a' | 'b' > type ExtractUser2 = Extract<keyof User, keyof OtherUser> type Omit<T, K extends keyof any > = Pick<T, Exclude<keyof T, K>>type OmitName = Omit<User, 'name' >type OmitName = {age: number , id: number } type Readonly<T> = { readonly [P in keyof T]: T[P]; }; const readonlyUser: Readonly<Pick<User, 'name' >> = { name: 'cpp' } readonlyUser.name = 'wmh' type Required<T> = { [P in keyof T] -? : T[p] } const testUser: Partial<User> = { id: 111 } const testUser2: Required<User> = { id: 121 }
基本类型 重点介绍使用场景和代码实例
enum
TypeScript支持数字的和基于字符串的枚举
反向映射: 对于 number 类型的枚举值, 会产生一个对应下标的索引, 如下: up: 1, 1: ‘up’
1 2 3 4 5 6 7 enum Direction { Up = 1 , Down, Left, Right } console .log(Dirction)
unknown unknown指的是不可预先定义的类型,在很多场景下,它可以替代any的功能同时保留静态检查的能力。 unknown的一个使用场景是,避免使用any作为函数的参数类型而导致的静态类型检查bug
1 2 3 4 5 6 function test (input: unknown ): number { if (Array .isArray(input)) { return input.length; } return input.length; }
可索引的类型 可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型
1 2 3 4 5 interface StringArray { [index: number ]: string } let myArr: StringArray = ['cpp' , 'chendap' ]let myArr2: StringArray = [1 , '33' ]
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 , length: 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!() }
可选链运算符 ?. ?. 用来判断左侧表达式是否是NUll | undefined ,如果是则停止运行程序,减少大量的 && 运算
1 2 3 4 5 6 7 8 9 if ( a && Reflect.has(a, 'b' ) && a.b ) { } if (a?.b) {}
比如我们写出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 > type Two = Foo<{t: boolean }> type Three = Foo<{a: number , t: () => 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 ) => { });
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 export function flat (arr ) { return arr.reduce((pre, cur ) => pre.concat(Array .isArray(cur) ? flat(cur) : cur), []) }
参考