# 定义变量的类型这一部分主要介绍类型系统中类型的简单定义方式,阅读完此章即可上手开发满足大部分开发需求情况
# 常用类型定义# 三大常用基础类型
1 2 3 let type1 : string let type2 : number let type3 : boolean
# 字面量
1 2 3 let font : 'hello' let font : 1 | 2
# bigint 和 symbol
1 2 let big : bigint = 100n ;let sym : symbol = Symbol ("lin" );
# null 和 undefined
1 2 let typeN : null let typeU : undefined
是任意类型子类型,strictNullChecks 如果 on,则严格 null 和 undefined
1 2 3 4 function liveDangerously (x?: number | null ) { console .log (x!.toFixed ()); }
ts 中使用 !.
类似于类型断言,可以明确该变量不为 null 和 undefined
# 数组
1 2 3 4 5 6 7 8 9 10 11 12 13 let typeArr1 : string []let typeArr2 : number []let typeArr3 : boolean []let typeArrY :[string , number , boolean ] = ['123' , 123 , true ]let arr :readonly string []let arr :ReadonlyArray <Type >let arr :readonly [string , number ]
# 内置对象
1 2 3 const date : Date = new Date ()const err : Error = new Error ('Error!' );const reg : RegExp = /abc/ ;
# DOM 和 BOM
1 2 3 4 5 6 let body : HTMLElement = document .body let allDiv : NodeList = document .querySelectorAll ('div' );document .addEventListener ('click' , (e: MouseEvent ) => { e.preventDefault () });
# any 和 unknown 还有 never
1 2 3 let type : any let typeU : unknown let typeN : never
# 模板文字类型
1 2 type World = "world" ;type Greeting = `hello ${World} ` ;
更多关于模板文字示例前往官网查看
# 联合类型
1 2 let type : string | number
如果类型不定义则默认为 any,any 对于类型系统来说放弃类型校验,是不好的,尽量使用 unknown 配合类型断言或是条件判断,让类型使用更安全一些
1 2 3 function divide (param: unknown ) { return param as number / 2 ; }
类型断言:在变量后使用 as [type]
如上事例,可以告诉类型系统这里的类型一定是什么,可以跳过类型系统安全检查
# 函数# 函数参数和返回值的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 function add (x: number , y: number , z:string | number , o: {name: string }, f:(n:number ) => void ): number { return x + y } function sum ({ a, b, c }: { a: number ; b: number ; c: number } ) { console .log (a + b + c); } function v ( ): void { }
# 箭头函数
1 2 3 4 let add2 = (x : number , y : number ): number => x + ylet add2 = (x : number = 100 , y?: number ): number => x + y
可选参数在使用时会有 undefined 警告,可以用默认参数来避免 可选参数为对象,可以用解构写法配合默认参数来避免 当然在明确该参数指定不需要默认时,可以用条件判断进行约束
# 函数重载~~(用泛型更加优雅,不建议重载写法)~~
1 2 function add (x: number [] ): number function add (x: string [] ): string
2023.9.20 日更新: 一次重构公司项目的 axios 函数时发现了关于函数重载的新的理解,函数重载的用法不仅仅如上面 Demo 这么简单,在某些时候发挥着泛型无法替代的作用 如下示例中
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 interface Person { name : string ; age : number ; } interface Boy extends Person { gender : 'man' ; } interface Girl extends Person { gender : 'women' ; } function xxx (params: Boy ): string ;function xxx (params: Girl ): Promise <string >;function xxx (params: Boy | Girl ): string | Promise <string > { if (params.gender === 'man' ) { return 'isMan' ; } else { return new Promise <string >((resolve, reject ) => { resolve ('isGirl' ); }); } } const person1 : Boy = { name : 'nihao' , age : 13 , gender : 'man' , }; const person2 : Girl = { name : 'nihao' , age : 13 , gender : 'women' , }; const res = xxx (person1); const res2 = xxx (person2);
通过上面简单的示例,可以使 TS 类型系统通过函数的入参正确地返回所需要的返回值,确保类型提示正确。
类型推断:Typescript 对不显式定义的变量会根据赋值等情况自动推断其类型
# Call Signatures 和 Construct Signatures这两个概念在官方文档中提到,用的不多不做收录
# 泛型函数泛型函数通过 <Type>
符号定义,在使用时以同样写法输入,这种动态类型写法可以使函数更加通用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function firstElement<Type >(arr : Type []): Type | undefined { return arr[0 ]; } const s = firstElement (["a" , "b" , "c" ]);const n = firstElement ([1 , 2 , 3 ]);const u = firstElement ([]);function map<Input , Output >(arr : Input [], func : (arg: Input ) => Output ): Output [] { return arr.map (func); const parsed = map (["1" , "2" , "3" ], (n ) => parseInt (n));function combine<Type >(arr1 : Type [], arr2 : Type []): Type [] { return arr1.concat (arr2); } const arr = combine<string | number >([1 , 2 , 3 ], ["hello" ]);
泛型在许多代码中都会以 <T,S>
来命名,个人推荐用更语义化的写法
可以用 extends
约束其传入类型条件
extends
主要用于 interface 的继承使对象约束类型更丰富,或是泛型中的类型约束,使传入的类型更具体。 extends
在 Typescript 中,用约束 相对于继承 可以更好体现其作用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function longest<Type extends { length : number }>(a : Type , b : Type ) { if (a.length >= b.length ) { return a; } else { return b; } } const longerArray = longest ([1 , 2 ], [1 , 2 , 3 ]);const longerString = longest ("alice" , "bob" );const notOK = longest (10 , 100 );
官方文档提供三个例子避免泛型屎山写法,可以跳转学习
# 对象# 字面量定义
1 2 3 let obj : { name : string }
# 接口描述对象
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 interface Person { name : string age?: number readonly id : number } const obj : Person const arr : Person []type type1 : Person ['name' ] type Person2 = typeof arr[number ]; type UPerson = keyof Person ; type Person3 = { [p in keyof Person ]: string } type Person4 = { -readonly [p in keyof Person ]: string } type Person5 = { [p in keyof Person ]-?: string } type Person6 = { [p in keyof Person as NewKeyType ]: Type [Properties ] } interface ISum { (x :number ,y :number ):number } const add :ISum = (num1, num2 ) => { return num1 + num2 }
# 索引签名
1 2 3 4 5 6 7 8 9 interface RandomKey { [name : string ]: string } interface LikeArray { [name : number ]: string } const arr : LikeArray = ['hello' , 'lin' ]
接口可以进行重载,配合函数重载可以丰富函数,但重载写法会使代码过于臃肿,所以还是推荐用泛型定义
# 泛型对象
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 interface IPrint <T> { (arg : T): T } interface IPrint <T = number > { (arg : T): T } interface ILength { length : number } function printLength<T extends ILength >(arg : T): T { console .log (arg.length ) return arg } const userInfo = { name : 'lin' , age : '18' , } function getValues<T, K extends keyof T>(userInfo : T, keys : K[]): T[K][] { return keys.map (key => userInfo[key]) } getValues (userInfo, ['name' , 'age' ])
# 交叉类型
1 2 3 4 5 6 interface Person { name : string age : number } type Student = Person & { grade : number }
# 枚举
1 2 3 4 5 6 7 8 9 enum Direction { Up , Down , } console .log (Direction .Up ) console .log (Direction .Down ) console .log (Direction [0 ]) console .log (Direction [1 ])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 enum Direction { Up = 6 , Down , } console .log (Direction .Up ) console .log (Direction .Down ) enum Direction { Up = "UP" , Down = "DOWN" , Left = "LEFT" , Right = "RIGHT" , } const value = 'UP' if (value === Direction .Up ) { }
1 2 3 4 5 6 7 8 9 10 11 12 13 const enum Direction { Up = "UP" , Down = "DOWN" , Left = "LEFT" , Right = "RIGHT" , } const value = 'UP' if (value === Direction .Up ) { }
用的少,暂时跳过
# 类型别名
1 2 3 4 5 6 7 8 type param = string | number type Point = { x : number ; y : number ; }; let a : Point
type 和 interface 区别在于
继承:前者用 &,后者用 extends 重写:type 无法重写,interface 可以重写 # 类型获取和保护# 使用 typeof
运算符使用 typeof
运算符,可以获取变量的类型,通过条件判断可以保障类型安全性,这里用 switch
或者三元运算符也是可以的
1 2 3 4 5 6 7 8 function getLength (arg: number | string ): number { if (typeof arg === 'string' ) { return arg.length } else { return arg.toString ().length } }
typeof 非常强大,对于未知的变量类型,可以直接用 typeof 提取其类型(将变量 var
转换成类型 type
),当然针对函数后面会提到工具函数来更好地进行类型提取,如一些非自己定义的函数如 setTimeout 或其他库函数
# 使用 in
运算符in
运算符可以对对象中属性做判断,配合条件判断得到正确类型
1 2 3 4 5 6 7 8 9 10 type Fish = { swim : () => void };type Bird = { fly : () => void };function move (animal: Fish | Bird ) { if ("swim" in animal) { return animal.swim (); } return animal.fly (); }
# 使用 instanceof
运算符instanceof
运算符可以检测对象同上
1 2 3 4 5 6 7 function logValue (x: Date | string ) { if (x instanceof Date ) { console .log (x.toUTCString ()); } else { console .log (x.toUpperCase ()); } }
更多条件类型判断例子可以前往官方文档查看,这里只举个例子进行说明
# 类型谓词用 is
来肯定函数返回值类型
1 2 3 function isFish (pet: Fish | Bird ): pet is Fish { return (pet as Fish ).swim !== undefined ; }
# 工具类型直接跳转至官网查看即可
工具类型
模版工具