1. 基础类型 TypeScript 支持与 JavaScript 几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用。
1.1.boolean 最基本的数据类型就是简单的 true/false 值,在JavaScript 和 TypeScript 里叫做 boolean
(其它语言中也一样)。
1 2 3 let isDone : boolean = false ;isDone = true ;
1.2.number 和 JavaScript 一样,TypeScript 里的所有数字都是浮点数。 这些浮点数的类型是 number。 除了支持十进制和十六进制字面量,TypeScript 还支持 ECMAScript 2015中引入的二进制和八进制字面量。
1 2 3 4 let a1 : number = 10 let a2 : number = 0b1010 let a3 : number = 0o12 let a4 : number = 0xa
1.3.string JavaScript 程序的另一项基本操作是处理网页或服务器端的文本数据。 像其它语言里一样,我们使用 string
表示文本数据类型。 和 JavaScript 一样,可以使用双引号("
)或单引号('
)表示字符串。
1 2 3 4 5 let name :string = 'tom' name = 'jack' let age :number = 12 const info = `My name is ${name} , I am ${age} years old!`
1.4.undefined 与 null 在 JavaScript 中,undefined 和 null 是两个基本数据类型
在TypeScript中,它们各自的类型也是undefined和null,也就意味着它们既是实际的值,也是自己的类型
1 2 let u : undefined = undefined let n : null = null
1.5.Array TypeScript 像 JavaScript 一样可以操作数组元素
1 2 3 4 5 const list1 : number [] = [1 , 2 , 3 ]const list2 : Array <number > = [1 , 2 , 3 ]
1 2 const list3 : (string | number | boolean )[] = [123 , true , "ts" ]const list4 : Array <string | number | boolean > = [true , "ts" , 456 ]
1.6.Tuple 元组类型允许表示一个已知元素数量 和类型 的数组,各元素的类型不必相同
。
比如,可以定义一对值分别为 string
和 number
类型的元组。
1 2 3 let t1 : [string , number ]t1 = ['hello' , 10 ] t1 = [10 , 'hello' ]
当访问一个已知索引的元素,会得到正确的类型:
1 2 console .log (t1[0 ].substring (1 )) console .log (t1[1 ].substring (1 ))
元组和数组的区别
首先,数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。(可以放在对象或者元组中) 其次,元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型 1 2 3 4 5 const info : (string | number )[] = ["aaa" , 18 , 1.8 ]const item1 = info[0 ] const tInfo : [string , number , number ] = ["aaa" , 18 , 1.8 ]const item2 = tInfo[0 ]
应用场景
1 2 3 4 5 6 7 8 9 10 11 12 13 function useState (initialState: number ): [number , (newValue: number ) => void ] { let stateValue = initialState function setValue (newValue: number ) { stateValue = newValue } return [stateValue, setValue] } const [count, setCount] = useState (10 )console .log (count)setCount (100 )
1.7.enum enum
类型是对 JavaScript 标准数据类型的一个补充。 使用枚举类型可以为一组数值赋予友好的名字
。
1 2 3 4 5 6 7 8 9 10 enum Color { Red , Green , Blue } const myColor : Color = Color .Green console .log (myColor, Color .Red , Color .Blue )
默认情况下,从 0
开始为元素编号。 也可以手动的指定成员的数值。 例如,将上面的例子改成从 1
开始编号:
1 2 3 4 5 6 enum Color { Red = 1 , Green , Blue } const c : Color = Color .Green
或者,全部都采用手动赋值:
1 2 enum Color {Red = 1 , Green = 2 , Blue = 4 }const lor = Color .Green
枚举类型可以由枚举的值得到它的名字。 例如,知道数值为 2,但是不确定它映射到 Color 里的哪个名字,可以查找相应的名字
1 2 3 4 enum Color {Red = 1 , Green , Blue }const colorName : string = Color [2 ]console .log (colorName)
使用场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 enum Direction { LEFT , RIGHT } const d1 : Direction = Direction .LEFT function turnDirection (direction: Direction ) { switch (direction) { case Direction .LEFT : console .log ("角色向左移动一个格子" ) break case Direction .RIGHT : console .log ("角色向右移动一个格子" ) break } } turnDirection (Direction .LEFT )
1.8.any 有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any
类型来标记这些变量:
1 2 3 let notSure : any = 4 notSure = 'maybe a string' notSure = false
在对现有代码进行改写的时候,any
类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。并且当你只知道一部分数据的类型时,any
类型也是有用的。 比如,你有一个数组,它包含了不同的类型的数据:
1 2 3 const list : any [] = [1 , true , 'free' ]list[1 ] = 100
1.9.void 某种程度上来说,void
类型像是与 any
类型相反,它表示没有任何类型
。 当一个函数没有返回值时,你通常会见到其返回值类型是 void
:
在TS中如果一个函数没有任何的返回值, 那么返回值的类型就是void类型
如果返回值是void
类型, 那么我们也可以返回undefined
(TS编译器允许这样做而已), null
不可以
1 2 3 4 5 6 function fn ( ): void { console .log ('fn()' ) }
声明一个 void
类型的变量没有什么大用,因为你只能为它赋予 undefined
:
1.10.Object object
表示非原始类型,也就是除 number
,string
,boolean
之外的类型。
使用 object
类型,就可以更好的表示像 Object.create
这样的 API
。例如:
1 2 3 4 5 6 7 8 9 function fn2 (obj:object ):object { console .log ('fn2()' , obj) return {} } console .log (fn2 (new String ('abc' )))console .log (fn2 (String ))
1.11.Symbol 在ES5中,是不可以在对象中添加相同的属性名称的,比如下面的做法:
1 2 3 4 const person = { identity : "aaa" , identity : "bbb" }
通常的做法是定义两个不同的属性名字:比如identity1和identity2
但是也可以通过symbol来定义相同的名称,因为Symbol函数返回的是不同的值
1 2 3 4 5 6 7 const s1 : symbol = Symbol ("title" )const s2 : symbol = Symbol ("title" )const person = { [s1]: "aaa" , [s2]: "bbb" }
1.12.unkkown unknown类型默认情况下在上面进行任意的操作都是非法的 要求必须进行类型的校验(缩小) , 才能根据缩小之后的类型, 进行对应的操作
1 2 3 4 5 6 let foo : unknown = "aaa" foo = 123 if (typeof foo === "string" ) { console .log (foo.length , foo.split (" " )) }
1.13.never never
表示永远不会发生值的类型,比如一个函数如果一个函数中是一个死循环或者抛出一个异常,那么这个函数不会返回东西,那么写void
类型或者其他类型作为返回值类型都不合适,就可以使用never
类型 实际开发中只有进行类型推导时, 可能会自动推导出来是never
类型, 但是很少使用它 1 2 3 4 5 6 7 8 9 10 11 12 13 function foo ( ): never { while (true ) { console .log ("-----" ) } throw new Error ("123" ) } foo ()function parseLyric ( ) { return [] }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function handleMessage (message: string | number | boolean ) { switch (typeof message) { case "string" : console .log (message.length ) break case "number" : console .log (message) break case "boolean" : console .log (Number (message)) break default : const check : never = message } } handleMessage ("aaaa" )handleMessage (1234 )handleMessage (true )handleMessage ({ name : "aaa" })
2.进阶类型 2.1.联合类型 联合类型(Union Types)表示取值可以为多种类型中的一种
TypeScript的类型系统允许我们使用多种运算符,从现有类型中构建新类型
使用第一种组合类型的方法:联合类型(Union Type)
联合类型是由两个或者多个其他类型组成的类型 表示可以是这些类型中的任何一个值 联合类型中的每一个类型被称之为联合成员(union’s members) 需求1: 定义一个函数得到一个数字或字符串值的字符串形式值
1 2 3 function toString2 (x: number | string ) : string { return x.toString () }
需求2: 定义一个函数得到一个数字或字符串值的长度
1 2 3 4 5 6 7 8 9 10 function getLength (x: number | string ) { if (x.length ) { return x.length } else { return x.toString ().length } }
处理多种类型问题
需要使用缩小(narrow)联合 TypeScript可以根据我们缩小的代码结构,推断出更加具体的类型 1 2 3 4 5 6 7 function getLength (x: number | string ) { if (typeof x === 'string' ) { return x.length } else { return x.toString ().length } }
2.2.类型断言 通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”
类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构
它没有运行时的影响,只是在编译阶段起作用。 TypeScript 会假设你,程序员,已经进行了必须的检查
类型断言有两种形式:其一是“尖括号”语法;另一个为 as
语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function getLength (x: number | string ) { if ((<string >x).length ) { return (x as string ).length } else { return x.toString ().length } } console .log (getLength ('abcd' ), getLength (1234 ))
1 2 3 4 5 6 7 8 9 const age : number = 18 const age3 = age as any const age4 = age3 as string console .log (age4.split (" " ))
2.3.类型推断 类型推断: TS会在没有明确的指定类型的时候推测出一个类型,有下面2种情况:
定义变量时赋值了, 推断为对应的类型 定义变量时没有赋值, 推断为any类型 1 2 3 4 5 6 7 8 let b9 = 123 let b10 b10 = 123 b10 = 'abc'
2.4.类型别名 以前通过在类型注解中编写 对象类型 和 联合类型,如果想要多次在其他地方使用时,就要编写多次,就可以给对象类型起一个别名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type MyNumber = number const age : MyNumber = 18 type IDType = number | string function printID (id: IDType ) { console .log (id) } type PointType = { x : number , y : number , z?: number }function printCoordinate (point: PointType ) { console .log (point.x , point.y , point.z ) }
2.5.接口声明 可以通过interface
可以用来声明一个对象类型,此处为了和type
声明作对比,简要说明基本用法,详细见接口章节
1 2 3 4 5 6 7 8 9 10 11 12 13 type PointType = { x : number y : number z?: number } interface PointType2 { x : number y : number z?: number }
2.6.类型与接口对比 两者区别
类型别名和接口非常相似,在定义对象类型时,大部分时候,可以任意选择使用 接口的几乎所有特性都可以在 type
中使用(后续会有interface
的很多特性) 如果是定义非对象类型,通常推荐使用type
,比如Direction
、Alignment
、一些Function
interface
可以重复的对某个接口来定义属性和方法,而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 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 type MyNumber = number type IDType = number | string type PointType1 = { x : number y : number } interface PointType2 { x : number y : number } interface PointType2 { z : number } const point : PointType2 = { x : 100 , y : 200 , z : 300 } interface IPerson { name : string age : number } interface IKun extends IPerson { kouhao : string } const ikun1 : IKun = { kouhao : "你干嘛, 哎呦" , name : "kobe" , age : 30 } class Person implements IPerson { }
2.7.交叉类型 另外一种类型合并,就是交叉类型(Intersection Types)
交叉类似表示需要满足多个类型的条件 交叉类型使用 &
符号 交叉类型栗子:
1 type MyType = number & string
在开发中,进行交叉时,通常是对对象类型进行交叉的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type NewType = number & string interface IKun { name : string age : number } interface ICoder { name : string coding : () => void } type InfoType = IKun & ICoder const info : InfoType = { name : "why" , age : 18 , coding : function ( ) { console .log ("coding" ) } }
2.8.非空类型断言 非空断言使用的是 !
,表示可以确定某个标识符是有值的,跳过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 interface IPerson { name : string age : number friend?: { name : string } } const info : IPerson = { name : "why" , age : 18 } console .log (info.friend ?.name )if (info.friend ) { info.friend .name = "kobe" } info.friend !.name = "james"
2.9.字面量类型 1 2 3 4 5 6 7 const name : "why" = "why" let age : 18 = 18 type Direction = "left" | "right" | "up" | "down" const d1 : Direction = "left"
2.10.字面量推理 栗子中:对象在进行字面量推理的时候,info其实是一个 {url: string, method: string}
,所以不能将一个 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 type MethodType = "get" | "post" function request (url: string , method: MethodType ) {} request ("http://www.aaa.com" , "post" )const info = { url : "xxxx" , method : "post" } request (info.url , info.method )request (info.url , info.method as "post" )const info2 : { url : string , method : "post" } = { url : "xxxx" , method : "post" } const info3 = { url : "xxxx" , method : "post" } as const request (info3.url , info3.method )
2.11.类型缩小 类型缩小的含义
类型缩小的英文是 Type Narrowing(也有人翻译成类型收窄) 可以通过类似于 typeof padding === "number"
的判断语句,来改变TypeScript的执行路径 在给定的执行路径中,可以缩小比声明时更小的类型,这个过程称之为 缩小( Narrowing ) 而编写的 typeof padding === "number"
可以称之为 类型保护(type guards) 常见的类型保护有如下几种:
typeof 平等缩小(比如===、!==) instanceof in 等等… 2.11.1.typeof 在 TypeScript 中,检查返回的值typeof
是一种类型保护,TypeScript 对如何typeof
操作不同的值进行编码
1 2 3 4 5 6 7 8 function printID (id: number | string ) { if (typeof id === "string" ) { console .log (id.length , id.split (" " )) } else { console .log (id) } }
2.11.2.平等缩小 可以使用Switch
或者相等的一些运算符来表达相等性(比如===, !==, ==, and != )
1 2 3 4 5 6 7 8 9 10 11 12 13 type Direction = "left" | "right" | "up" | "down" function switchDirection (direction: Direction ) { if (direction === "left" ) { console .log ("左:" , "角色向左移动" ) } else if (direction === "right" ) { console .log ("右:" , "角色向右移动" ) } else if (direction === "up" ) { console .log ("上:" , "角色向上移动" ) } else if (direction === "down" ) { console .log ("下:" , "角色向下移动" ) } }
2.11.3.instanceof JavaScript
有一个运算符instanceof
来检查一个值是否是另一个值的“实例”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function printDate (date: string | Date ) { if (date instanceof Date ) { console .log (date.getTime ()) } else { console .log (date) } }
2.11.4.in操作符 Javascript
有一个运算符,用于确定对象是否具有带名称的属性:in运算符
如果指定的属性在指定的对象或其原型链中,则in
运算符返回true
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 interface ISwim { swim : () => void } interface IRun { run : () => void } function move (animal: ISwim | IRun ) { if ("swim" in animal) { animal.swim () } else if ("run" in animal) { animal.run () } } const fish : ISwim = { swim : function ( ) {} } const dog : IRun = { run : function ( ) {} } move (fish)move (dog)
3. 函数 函数是 JavaScript 应用程序的基础,可以实现抽象层,模拟类,信息隐藏和模块。在 TypeScript 里,虽然已经支持类,命名空间和模块,但函数仍然是主要的定义行为的地方。TypeScript 为 JavaScript 函数添加了额外的功能,可以更容易地使用
3.1.基本示例 和 JavaScript 一样,TypeScript 也可以创建函数。
1 2 3 4 5 6 7 8 9 function add (x, y ) { return x + y } const myAdd = function (x, y ) { return x + y }
3.2.函数类型 3.2.1.定义函数类型 让我们为上面那个函数添加类型:
1 2 3 4 5 6 7 function add (x: number , y: number ): number { return x + y } const myAdd = function (x: number , y: number ): number { return x + y }
我们可以给每个参数添加类型之后再为函数本身添加返回值类型。TypeScript 能够根据返回语句自动推断出返回值类型。
3.2.2.完整函数类型 现在我们已经为函数指定了类型,下面让我们写出函数的完整类型。
1 2 3 4 5 6 7 8 9 10 const myAdd2 : (x: number , y: number ) => number = function (x: number , y: number ): number { return x + y } type addFnType = (x: number , y: number ) => number const myAdd2 : addFnTyep = function (x: number , y: number ): number { return x + y }
3.3.参数相关 3.3.1.可选参数 JavaScript 里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是 undefined
。 在TypeScript 里我们可以在参数名旁使用 ?
实现可选参数的功能
这个时候y
的类型其实是 undefined
和 number
类型的联合。
1 2 3 4 5 6 7 function add (X: number , y?: number ): number { if (y) { return x + y } else { return x } }
3.3.2.默认参数 在 TypeScript 里,也可以为参数提供一个默认值,当用户没有传递这个参数或传递的值是 undefined
时。 它们叫做有默认初始化值的参数
1 2 3 function add (x: number , b: number = 6 ): number { return x + y }
3.3.3.剩余参数 必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。 有时,想同时操作多个参数,或者并不知道会有多少参数传递进来。 在 JavaScript 里,你可以使用 arguments
来访问所有传入的参数。
在 TypeScript 里,你可以把所有参数收集到一个变量里: 剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( ...
)后面给定的名字,你可以在函数体内使用这个数组。
1 2 3 4 function info (x: string , ...args: string [] ) { console .log (x, args) } info ('abc' , 'c' , 'b' , 'a' )
3.4.函数重载 函数重载: 函数名相同, 而形参不同的多个函数 在JS中, 由于弱类型的特点和形参与实参可以不匹配, 是没有函数重载这一说的 但在TS中, 与其它面向对象的语言(如Java)就存在此语法
在TypeScript中,可以去编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用 一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现 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 function add (arg1, arg2 ) { return arg1 + arg2 } add (10 , 20 )add ("abc" , "cba" )add ({aaa : "aaa" }, 123 )function add1 (num1: number , num2: number ) { return num1 + num2 } function add2 (str1: string , str2: string ) { return str1 + str2 } add1 (10 , 20 )add2 ("abc" , "cba" )function add (arg1: number | string , arg2: number | string ) { return arg1 + arg2 } function add (arg1: number , arg2: number ): number function add (arg1: string , arg2: string ): string function add (arg1: any , arg2: any ): any { return arg1 + arg2 } add (10 , 20 )add ("aaa" , "bbb" )
需求:定义一个函数,可以传入字符串或者数组,获取它们的长度
两种实现方案
方案一:使用联合类型来实现 方案二:实现函数重载来实现 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 function getLength (arg ) { return arg.length } function getLength (arg: string ): number function getLength (arg: any [] ): number function getLength (arg ) { return arg.length } function getLength (arg: string | any [] ) { return arg.length } function getLength (arg: { length: number } ) { return arg.length } getLength ("aaaaa" )getLength (["abc" , "cba" , "nba" ])getLength ({ length : 100 })
3.5.调用签名 在 JavaScript 中,函数除了可以被调用,自己也是可以有属性值的
然而前面讲到的函数类型表达式并不能支持声明属性
如果想描述一个带有属性的函数,可以在一个对象类型中写一个调用签名(call signature
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type BarType = (num: number ) => number interface IBar { name : string age : number (num : number ): number } const bar : IBar = (num : number ): number => { return num } bar.name = "aaa" bar.age = 18 bar (123 )
3.6.构造签名 JavaScript 函数也可以使用 new
操作符调用,当被调用的时候,TypeScript 会认为这是一个构造函数(constructor
),因为 他们会产生一个新对象
可以写一个构造签名( Construct Signatures ),方法是在调用签名前面加一个 new
关键词 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Person { constructor ( ) { } } interface ICTORPerson { new (): Person } function factory (fn: ICTORPerson ) { const f = new fn () return f } factory (Person )
3.7.this相关 3.7.1.函数中this默认类型 在tsconfig.json
设置了noImplicitThis
为true
时, TypeScript会根据上下文推导this
,但是在不能正确推导时,就会报错,需要明确的指定this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const obj = { name : "why" , studying : function ( ) { console .log (this .name .length , "studying" ) } } obj.studying () function foo ( ) { console .log (this ) }
3.7.2.函数中this明确类型 在开启noImplicitThis
的情况下,必须指定this
的类型
函数的第一个参数类型
函数的第一个参数可以根据该函数之后被调用的情况,用于声明this
的类型(名词必须叫this
) 在后续调用函数传入参数时,从第二个参数开始传递的,this
参数会在编译后被抹除 1 2 3 4 5 6 function foo (this : { name: string }, info: {name: string } ) { console .log (this , info) } foo.call ({ name : "Tom" }, { name : "kobe" })
3.7.3.this相关内置工具 Typescript 提供了一些工具类型来辅助进行常见的类型转换,这些类型全局可用
3.7.3.1.ThisParameterType ThisParameterType
用于提取一个函数类型Type
的this
(opens new window)参数类型 如果这个函数类型没有this
参数返回unknown
1 2 3 4 5 6 7 8 function foo (this : { name: string }, info: {name: string } ) { console .log (this , info) } type FooType = typeof footype FooThisType = ThisParameterType <FooType >
3.7.3.2.OmitThisParameter OmitThisParameter
用于移除一个函数类型Type
的this
参数类型, 并且返回当前的函数类型 1 2 3 4 5 6 7 8 function foo (this : { name: string }, info: {name: string } ) { console .log (this , info) } type FooType = typeof footype PureFooType = OmitThisParameter <FooType >
3.7.3.3.ThisType 这个类型不返回一个转换过的类型,它被用作标记一个上下文的this
类型
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 IState { name : string age : number } interface IStore { state : IState eating : () => void running : () => void } const store : IStore & ThisType <IState > = { state : { name : "aaa" , age : 18 }, eating ( ) { console .log (this .name ) }, running ( ) { console .log (this .name ) } } store.eating .call (store.state )
4. 接口 TypeScript 的核心原则之一是对值所具有的结构进行类型检查。我们使用接口(Interfaces
)来定义对象的类型。接口是对象的状态(属性)和行为(方法)的抽象(描述)
4.1.接口初探 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 interface IPerson { id : number name : string age : number sex : string } const person1 : IPerson = { id : 1 , name : 'tom' , age : 20 , sex : '男' }
类型检查器会查看对象内部的属性是否与IPerson接口描述一致, 如果不一致就会提示类型错误
4.2.可选属性 接口里的属性不全都是必需的,有些是只在某些条件下存在,或者根本不存在。 带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个 ?
符号 1 2 3 4 5 6 interface IPerson { id : number name : string age : number sex?: string }
可选属性的优势
可以对可能存在的属性进行预定义 可以捕获引用了不存在的属性时的错误 1 2 3 4 5 6 const person2 : IPerson = { id : 1 , name : 'tom' , age : 20 , }
4.3.只读属性 一些对象属性只能在对象刚刚创建的时候修改其值,可以在属性名前用 readonly
来指定只读属性:
1 2 3 4 5 6 interface IPerson { readonly id : number name : string age : number sex?: string }
一旦赋值后再也不能被改变了
1 2 3 4 5 6 7 8 const person2 : IPerson = { id : 2 , name : 'tom' , age : 20 , } person2.id = 2
readonly vs const
最简单判断用 readonly
还是 const
的方法是,看要把它做为变量使用还是做为一个属性 做为变量使用的话用 const
,若做为属性则使用 readonly
4.4.函数类型 接口能够描述 JavaScript 中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。
为了使用接口表示函数类型,给接口定义一个调用签名 它就像是一个只有参数列表和返回值类型的函数定义 参数列表里的每个参数都需要名字和类型 1 2 3 4 5 6 7 8 9 10 11 12 13 interface SearchFunc { (source : string , subString : string ): boolean } const mySearch : SearchFunc = function (source: string , sub: string ): boolean { return source.search (sub) > -1 } console .log (mySearch ('abcd' , 'bc' ))
4.5.类实现接口 接口定义后,也是可以被类实现的,使用implements
如果被一个类实现,那么在之后需要传入接口的地方,都可以将这个类传入 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 interface IKun { name : string age : number slogan : string playBasketball : () => void } interface IRun { running : () => void } const ikun : IKun = { name : "why" , age : 18 , slogan : "你干嘛!" , playBasketball : function ( ) { console .log ("playing basketball" ) } } class Person implements IKun , IRun { name : string age : number slogan : string playBasketball ( ) { console .log ("playing basketball" ) } running ( ) { console .log ("running" ) } } const ikun2 = new Person ()const ikun3 = new Person ()const ikun4 = new Person ()console .log (ikun2.name , ikun2.age , ikun2.slogan )ikun2.playBasketball () ikun2.running ()
4.6.接口的继承 接口和类一样是可以进行继承的,也是使用extends
关键字 接口是支持多继承的(类不支持多继承) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 interface IPerson { name : string age : number } interface IKun extends IPerson { slogan : string } const ikun : IKun = { name : "kun" , age : 18 , slogan : "你干嘛, 哎呦" }
5. 类 对于传统的 JavaScript 程序我们会使用函数
和基于原型的继承
来创建可重用的组件,但对于熟悉使用面向对象方式的程序员使用这些语法就有些棘手,因为他们用的是基于类的继承
并且对象是由类构建出来的。 从 ECMAScript 2015,也就是 ES6 开始, JavaScript 程序员将能够使用基于类的面向对象的方式。 使用 TypeScript,我们允许开发者现在就使用这些特性,并且编译后的 JavaScript 可以在所有主流浏览器和平台上运行,而不需要等到下个 JavaScript 版本。
5.1.定义 类的声明:使用class
关键字
声明类的属性:在类的内部声明类的属性以及对应的类型
如果类型没有声明,那么它们默认是any的
也可以给属性设置初始化值
在默认的strictPropertyInitialization
模式下面我们的属性是必须 初始化的,如果没有初始化,那么编译时就会报错
如果在strictPropertyInitialization
模式下确实不希望给属性初始化,可以使用 name!: string
语法 类可以有自己的构造函数constructor
,通过new
关键字创建 一个实例时,构造函数会被调用
构造函数不需要返回任何值,默认返回当前创建出来的实例
类中可以有自己的函数,定义的函数称之为方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Person { name!: string age : number constructor (name: string , age: number ) { this .age = age } running ( ) { console .log (this .name + " running!" ) } eating ( ) { console .log (this .name + " eating!" ) } }
5.2.继承 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 class Person { name!: string age : number constructor (name: string , age: number ) { this .name = name this .age = age } running ( ) { console .log (this .name + " running!" ) } eating ( ) { console .log (this .name + " eating!" ) } } class Student extends Person { sno : number constructor (name: string , age: number , sno: numebr ) { super (name, age) this .sno = sno } studying ( ) { console .log (this .name + " studying!" ) } running ( ) { super .running () console .log ("Student running!" ) } eating ( ) { console .log ("Student eating!" ) } }
5.3.多态 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 class Animal { name : string constructor (name : string ) { this .name = name } run (distance: number = 0 ) { console .log (`${this .name} run ${distance} m` ) } } class Snake extends Animal { constructor (name : string ) { super (name) } run (distance: number = 5 ) { console .log ('sliding...' ) super .run (distance) } } class Horse extends Animal { constructor (name: string ) { super (name) } run (distance: number = 50 ) { console .log ('dashing...' ) super .run (distance) } xxx ( ) { console .log ('xxx()' ) } } const snake = new Snake ('sn' )snake.run () const horse = new Horse ('ho' )horse.run () const tom : Animal = new Horse ('ho22' )tom.run () const tom2 : Snake = new Animal ('tom2' )tom2.run () const tom3 : Horse = new Animal ('tom3' )tom3.run ()
5.4.修饰符 在TypeScript中,类的属性和方法支持三种修饰符: public
、private
、protected
public
修饰的是在任何地方可见、公有的属性或方法,默认编写的属性就是public
,可以直接访问private
修饰的是仅在同一类中可见、私有的属性或方法protected
修饰的是仅在类自身及子类中可见、受保护的属性或方法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 class Person { protected name : string private age : number public gerder : string constructor (name: string , age: number , gender: string ) { this .name = name this .age = age this .gender = gender } private eating ( ) { console .log ("吃东西" , this .age , this .name ) } } const p = new Person ("aaa" , 18 )class Student extends Person { constructor (name: string , age: number ) { super (name, age) } studying ( ) { console .log ("在学习" , this .name ) } } const stu = new Student ("bbb" , 18 )
5.5.readonly 你可以使用 readonly
关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化
1 2 3 4 5 6 7 8 9 class Person { readonly name : string = 'abc' constructor (name: string ) { this .name = name } } const person = new Person ('cba' )
5.6.参数属性 TypeScript 提供了特殊的语法,可以把一个构造函数参数转成一个同名同值的类属性
这些被称为参数属性(parameter properties) 可以通过在构造函数参数前添加一个可见性修饰符 public
、private
、protected
或者 readonly
来创建参数属性,最后这些类属性字段也会得到这些修饰符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Person { constructor (public name: string , private _age: number , readonly height: number , protected gender: string ) { } running ( ) { console .log (this ._age , "eating" ) } } const p = new Person ("aaa" , 18 , 1.88 , "male" )console .log (p.name , p.height )
5.7.存取器 在前面一些私有属性是不能直接访问的,或者某些属性想要监听它的获取(getter
)和设置(setter
)的过程,这个时候可以使用存取器
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 class Person { private _name : string private _age : number constructor (name: string , age: number ) { this ._name = name this ._age = age } running ( ) { console .log ("running:" , this ._name ) } set name (newValue: string ) { this ._name = newValue } get name () { return this ._name } set age (newValue: number ) { if (newValue >= 0 && newValue < 200 ) { this ._age = newValue } } get age () { return this ._age } } const p = new Person ("aaa" , 100 )p.name = "kobe" console .log (p.name ) p.age = -10 console .log (p.age )
5.8.静态属性 到目前为止,只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上,使用static
关键字
使用 类名.静态属性名
来访问静态属性
1 2 3 4 5 6 7 8 9 10 11 12 class Person { name1 : string = 'A' static name2 : string = 'B' } console .log (Person .name2 )console .log (new Person ().name1 )
5.9.抽象类 继承是多态使用的前提
所以在定义很多通用的调用接口时, 通常会让调用者传入父类,通过多态来实现更加灵活的调用方式 但是,父类本身可能并不需要对某些方法进行具体的实现,所以父类中定义的方法,,可以定义为抽象方法 抽象方法:在TypeScript中没有具体实现的方法(没有方法体),就是抽象方法
抽象方法,必须存在于抽象类中 抽象类是使用abstract
声明的类 抽象类有如下的特点
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 abstract class Shape { abstract getArea (): number } class Rectangle extends Shape { constructor (public width: number , public height: number ) { super () } getArea ( ) { return this .width * this .height } } class Circle extends Shape { constructor (public radius: number ) { super () } getArea ( ) { return this .radius ** 2 * Math .PI } } class Triangle extends Shape { constructor (public a: number , public b: number , public c: number ) { super () } getArea ( ) { let p = 0 let s = 0 if (this .a + this .b < this .c || this .a + this .c < this .b || this .b + this .c < this .a ) { throw new Error ("不能构成三角形" ) } else { p = (this .a + this .b + this .c ) / 2 ; s = Math .sqrt (p * (p - this .a ) * (p - this .b ) * (p - this .c )); return s } } } function calcArea (shape: Shape ) { return shape.getArea () } calcArea (new Rectangle (10 , 20 ))calcArea (new Circle (5 ))calcArea (new Triangle (3 , 4 , 5 ))calcArea ({ getArea (): number { return 1 } })
5.10.类的类型 类本身也是可以作为一种数据类型的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Person { name : string constructor (name: string ) { this .name = name } running ( ) { console .log (this .name + " running!" ) } } const p1 = new Person ("aaa" )const p2 : Person = { name : "bbb" , running ( ) { console .log (this .name + " running!" ) } }
5.11.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 class Person { constructor (public name: string , public age: number ) { } running ( ) { } } class Dog { constructor (public name: string , public age: number ) { } running ( ) { } } function printPerson (p: Person ) { console .log (p.name , p.age ) } printPerson (new Person ("why" , 18 ))printPerson ({ name : "kobe" , age : 30 , running : function ( ) { } })printPerson (new Dog ("旺财" , 3 ))const person : Person = new Dog ("果汁" , 5 )
5.12.对象类型属性修饰符 对象类型中的每个属性可以说明它的类型、属性是否可选、属性是否只读等信息
可选属性(Optional Properties) 只读属性(Readonly Properties)在 TypeScript 中,属性可以被标记为 readonly
,这不会改变任何运行时的行为 但在类型检查的时候,一个标记为 readonly
的属性是不能被写入的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type IPerson = { name?: string readonly age : number } interface IKun { name?: string readonly slogan : string } const p : IPerson = { name : "why" , age : 18 }
5.13.索引签名 索引签名的含义
有的时候,不能提前知道一个类型里的所有属性的名字,但是知道这些值的特征 这种情况,就可以用一个索引签名 (index signature) 来描述可能的值的类型 索引签名的用法
一个索引签名的属性类型必须是 string
或者是 number
虽然 TypeScript 可以同时支持 string
和 number
类型,但数字索引的返回类型一定要是字符索引返回类型的子类型 5.13.1.基本使用 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 InfoType { [key : string ]: string } function getInfo ( ): InfoType { const abc : any = "haha" return abc } const info = getInfo ()console .log (info.name , info.age , info.address )interface ICollection { [index : number ]: string length : number } function printCollection (collection: ICollection ) { for (let i = 0 ; i < collection.length ; i++) { const item = collection[i] console .log (item.length ) } } const array = ["abc" , "cba" , "nba" ]const tuple : [string , string ] = ["aaa" , "北京" ]printCollection (array)printCollection (tuple)
5.13.2.类型问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 interface IIndexType { }
5.13.3.两个签名 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 interface IIndexType { [index : number ]: string [key : string ]: any } const names : IIndexType = ["abc" , "cba" , "nba" ]const item1 = names[0 ]const forEachFn = names["forEach" ]names["aaa" ]
5.14.严格的字面量赋值检测 This PR implements stricter obiect literal assianment checks for the purpose of catching excess or misspelled properties.
The PR implements the suggestions in #3755. Specifically:
Every object literal is initially considered “fresh” When a fresh object literal is assigned to a variable or passed for a parameter of a non-empty target type, it is an error for the object literal to specify properties that don’t exist in the target type. Freshness disappears in a type assertion or when the type of an object literal is widened. 简单对上面的英文进行翻译解释
每个对象字面量最初都被认为是“新鲜的(fresh)” 当一个新的对象字面量分配给一个变量或传递给一个非空目标类型的参数时,对象字面量指定目标类型中不存在的属性是错误的 当类型断言或对象字面量的类型扩大时,新鲜度会消失 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 interface IPerson { name : string age : number } const obj = { name : "aaa" , age : 18 , height : 1.88 } const info : IPerson = obj function printPerson (person: IPerson ) { }const kobe = { name : "kobe" , age : 30 , height : 1.98 }printPerson (kobe) const obj2 = { name : "why" , age : 18 , height : 1.88 } const p : IPerson = obj2
6.泛型 在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性
虽然any是可以的,但是定义为any的时候,其实已经丢失了类型信息
比如传入的是一个number,那么希望返回的可不是any类型,而是number类型 所以,需要在函数中可以捕获到参数的类型是number,并且同时使用它来作为返回值的类型 6.1.类型的参数化 通过 <类型>
的方式将类型传递给函数 通过类型推导(type argument inference),自动推到出传入变量的类型在这里会推导出它们是字面量类型的,因为字面量类型对于函数也是适用的 1 2 3 4 5 6 7 8 9 10 11 12 function bar<Type >(arg : Type ): Type { return arg } const res1 = bar<number >(123 )const res2 = bar<string >("abc" )const res3 = bar<{name : string }>({ name : "aaa" })const res4 = bar ("aaa" )const res5 = bar (111 )
react中useState
的应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function useState<Type >(initialState : Type ): [Type , (newState: Type ) => void ] { let state = initialState function setState (newState: Type ) { state = newState } return [state, setState] } const [count, setCount] = useState (100 )const [message, setMessage] = useState ("Hello World" )const [banners, setBanners] = useState<any []>([])
6.2.多个泛型参数 一个函数可以定义多个泛型参数
平时在开发中可能会看到一些常用的名称
T
:Type的缩写,类型K
、V
:key
和value
的缩写,键值对E
:Element
的缩写,元素O
:Object
的缩写,对象1 2 3 4 function foo <K, V> (key : K, value : V): [K, V] { return [key, value] } const result = foo<string , number >('age' , 18 )
6.3.泛型接口 在定义接口时, 为接口中的属性或方法定义泛型类型 在使用接口时, 再指定具体的泛型类型
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 interface IKun <Type = string > { name : Type age : number slogan : Type } const ikun1 : IKun <string > = { name : "kun" , age : 18 , slogan : "哎呦,你干嘛!" } const ikun2 : IKun <number > = { name : 123 , age : 20 , slogan : 666 } const ikun3 : IKun = { name : "ikun" , age : 30 , slogan : "坤坤加油!" }
6.4.泛型类 在定义类时, 为类中的属性或方法定义泛型类型 在创建类的实例时, 再指定特定的泛型类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Point <Type = number > { x : Type y : Type constructor (x: Type, y: Type ) { this .x = x this .y = y } } const p1 = new Point (10 , 20 )console .log (p1.x )const p2 = new Point ("123" , "321" )console .log (p2.x )
6.5.泛型约束 希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中
比如string
和array
都是有length
的,或者某些对象也是会有length
属性的 那么只要是拥有length
的属性都可以作为的参数类型 这里表示是传入的类型必须有这个属性,也可以有其他属性,但是必须至少有这个成员 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 interface ILength { length : number } function getLength (arg: ILength ) { return arg.length } const length1 = getLength ("aaaa" )const length2 = getLength (["aaa" , "bbb" , "ccc" ])const length3 = getLength ({ length : 100 })function getInfo<Type extends ILength >(args : Type ): Type { return args } const info1 = getInfo ("aaaa" )const info2 = getInfo (["aaa" , "bbb" , "ccc" ])const info3 = getInfo ({ length : 100 })
在泛型约束中使用类型参数(Using Type Parameters in Generic Constraints)
可以声明一个类型参数,这个类型参数被其他类型参数约束 举个栗子:希望获取一个对象给定属性名的值需要确保不会获取 obj 上不存在的属性 所以在两个类型之间建立一个约束 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 interface IKun { name : string age : number } type IKunKeys = keyof IKun function getObjectProperty<O, K extends keyof O>(obj : O, key : K) { return obj[key] } const info = { name : "aaa" , age : 18 , height : 1.88 } const name = getObjectProperty (info, "name" )
6.6.映射类型 有的时候,一个类型需要基于另外一个类型,但是又不想拷贝一份,这个时候可以考虑使用映射类型
大部分内置的工具都是通过映射类型来实现的 大多数类型体操的题目也是通过映射类型完成的 映射类型建立在索引签名 语法基础之上
映射类型,就是使用了 PropertyKeys
联合类型的泛型 其中 PropertyKeys
多是通过 keyof
创建,然后循环遍历键名创建一个类型 6.6.1.基本使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type MapPerson <Type > = { [Property in keyof Type ]: Type [Property ] } interface IPerson { name : string age : number } type NewPerson = MapPerson <IPerson >
6.6.2.修饰符使用 在使用映射类型时,有两个额外的修饰符可能会用到
一个是 readonly
,用于设置属性只读 一个是 ?
,用于设置属性可选 1 2 3 4 5 6 7 8 9 10 11 12 type MapPerson <Type > = { readonly [Property in keyof Type ]?: Type [Property ] } interface IPerson { name : string age : number height : number address : string } type IPersonOptional = MapPerson <IPerson >
6.6.3.修饰符符号 可以通过前缀 -
或者 +
删除或者添加这些修饰符
如果没有写前缀,相当于使用了 +
前缀(默认)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type MapPerson <Type > = { -readonly [Property in keyof Type ]-?: Type [Property ] } interface IPerson { name : string age?: number readonly height : number address?: string } type IPersonRequired = MapPerson <IPerson >const p : IPersonRequired = { name : "aaa" , age : 18 , height : 1.88 , address : "北京市" }
7.内置工具与类型体操 类型系统其实在很多语言里面都是有的,比如Java、Swift、C++等等,但是相对来说TypeScript的类型非常灵活
这是因为TypeScript的目的是为JavaScript添加一套类型校验系统,因为JavaScript本身的灵活性,也让TypeScript类型系统 不得不增加更附加的功能以适配JavaScript的灵活性 所以TypeScript是一种可以支持类型编程的类型系统 这种类型编程系统为TypeScript增加了很大的灵活度,同时也增加了它的难度
如果你不仅仅在开发业务的时候为自己的JavaScript代码增加上类型约束,那么基本不需要太多的类型编程能力 但是如果你在开发一些框架、库,或者通用性的工具,为了考虑各种适配的情况,就需要使用类型编程 TypeScript本身为我们提供了类型工具,帮助我们辅助进行类型转换(前面有用过关于this的类型工具)
很多开发者为了进一步增强自己的TypeScript编程能力,还会专门去做一些类型体操的题目
7.1.条件类型 很多时候,日常开发中需要基于输入的值来决定输出的值,同样也需要基于输入的值的类型来决定输出的值的类型
条件类型(Conditional types)就是用来描述输入类型和输出类型之间的关系
条件类型的写法有点类似于 JavaScript 中的条件表达式(condition ? trueExpression : falseExpression ): 条件类型(Conditional Types) SomeType extends OtherType ? TrueType : FalseType;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type IDType = number | string type ResType = boolean extends IDType ? true : false function sum<T extends number | string >(num1 : T, num2 : T): T extends number ? number : string function sum (num1: any , num2: any ) { return num1 + num2 } const res = sum (20 , 30 )const res2 = sum ("abc" , "cba" )
7.2.条件类型中的推断 在条件类型中推断(Inferring Within Conditional Types)
条件类型提供了 infer
关键词,可以从正在比较的类型中推断类型,然后在 true
分支里引用该推断结果 比如现在有一个函数类型,想要获取到一个函数的参数类型和返回值类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type CalcFnType = (num1: numebr, num2: number ) => number function foo ( ) { return "abc" } type MyReturnType <T extends (...args : any []) => any > = T extends (...args : any []) => infer R ? R : never type MyParameterType <T extends (...args; any []) => any > = T extends (...args : infer P) => any ? P : never type CalcReturnType = MyReturnType <CalcFnType >type FooReturnType = MyReturnType <typeof foo>type CalcParameterType = MyParameterType <CalcFnType >
7.3.分发条件类型 当在泛型中使用条件类型 的时候,如果传入一个联合类型 ,就会变成 分发的 (distributive)
如果在 ToArray
传入一个联合类型,这个条件类型会被应用到联合类型的每个成员
当传入string | number
时,会遍历联合类型中的每一个成员 相当于ToArray | ToArray
所以最后的结果是:string[] | number[]
1 2 3 4 5 6 7 type toArray<T> = T extends any ? T[] : never type NumArray = toArray<number > type NumAndStrArray = toArray<number | string >
7.4.内置工具 7.4.1.Partial 用于构造一个Type下面的所有属性都设置为可选的类型
格式:Partial<Type>
1 2 3 4 5 6 7 8 9 10 11 12 13 interface IKun { name : string age : number slogan?: string } type MyPartial <T> = { [P in keyof T]?: T[P] } type IKunOptional = MyPartial <IKun >
7.4.2.Required 用于构造一个Type
下面的所有属性全都设置为必填的类型 ,这个工具类型跟 Partial
相反
格式:Required<Type>
1 2 3 4 5 6 7 8 9 10 11 12 13 interface IKun { name : string age : number slogan?: string } type MyRequired <T> = { [P in keyof T]-?: T[P] } type IKunRequire = MyRequired <IKun >
7.4.3.Readonly 用于构造一个Type
下面的所有属性全都设置为只读的类型 ,意味着这个类型的所有的属性全都不可以重新赋值
格式:Readonly<Type>
1 2 3 4 5 6 7 8 9 10 11 12 13 interface IKun { name : string age : number slogan?: string } type MyReadonly <T> = { readonly [P in keyof T]: T[P] } type IKunReadonly = MyReadonly <IKun >
7.4.4.Record 用于构造一个对象类型,它所有的key
(键)都是Keys
类型,它所有的value
(值)都是Type
类型。
格式:Record<Keys, 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 25 26 27 28 29 30 31 32 33 interface IKun { name : string age : number slogan?: string } type Keys = keyof IKun type Res = keyof any type MyRecord <Keys extends keyof any , T> = { [P in Keys ]: T } type Citys = "上海" | "北京" | "洛杉矶" type IKuns = MyRecord <Citys , IKun >const ikuns : IKuns = { "上海" : { name : "xxx" , age : 10 }, "北京" : { name : "yyy" , age : 5 }, "洛杉矶" : { name : "zzz" , age : 3 } }
7.4.5.Pick 用于构造一个类型,它是从Type
类型里面挑了一些属性Keys
格式:Pick<Type, Keys>
1 2 3 4 5 6 7 8 9 10 11 12 13 interface IKun { name : string age : number slogan?: string } type MyPick <T, K extends keyof T> = { [P in K]: T[P] } type IKuns = MyPick <IKun , "slogan" | "name" >
7.4.6.Omit 用于构造一个类型,它是从Type
类型里面过滤(排除)一些属性Keys
用法:Omit<Type, Keys>
1 2 3 4 5 6 7 8 9 10 11 12 13 interface IKun { name : string age : number slogan?: string } type MyOmit <T, K extends keyof T> = { [P in keyof T as P extends K ? never : P]: T[P] } type IKuns = MyOmit <IKun , "slogan" | "name" >
7.4.7.Exclude 用于构造一个类型,它是从UnionType
联合类型里面排除了所有可以赋给ExcludedMembers
的类型
用法:Exclude<UnionType, ExcludedMembers>
1 2 3 4 5 6 7 type IKun = "sing" | "dance" | "rap" type MyExclude <T, E> = T extends E ? never : Ttype IKuns = MyExclude <IKun , "rap" >
用于构造一个类型,它是从Type
类型里面提取了所有可以赋给Union
的类型
用法:Extract<Type, Union>
1 2 3 4 5 6 7 type IKun = "sing" | "dance" | "rap" type MyExtract <T, E> = T extends E ? T : never type IKuns = MyExtract <IKun , "sing" | "dance" >
7.4.9.NonNullable 用于构造一个类型,这个类型从Type中排除了所有的null
、undefined
的类型
用法:NonNullable<T>
1 2 3 4 5 6 7 type IKun = "sing" | "dance" | "rap" | null | undefined type MyNonNullable <T> = T extends null | undefined ? never : Ttype IKuns = MyNonNullable <IKun >
7.4.10.ReturnType 用于构造一个含有Type
函数的返回值的类型
用法:ReturnType<T>
1 2 3 4 5 6 7 8 9 function iKun ( ) { return "哎呦,你干嘛!" } type MyReturnType <T extends (...args : any []) => any > = T extends (...args : any []) => infer R ? R : never type iKunReturnType = MyReturnType <typeof iKun>
7.4.11.InstanceType 用于构造一个由所有Type
的构造函数的实例类型组成的类型
用法:InstanceType<T>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Person { }class Dog { }type MyInstanceType <T extends new (...args : any []) => any > = T extends new (...args : any []) => infer R ? R : never const p1 : Person = new Person ()type MyPerson = MyInstanceType <typeof Person > const p2 : MyPerson = new Person ()function factory<T extends new (...args : any []) => any >(ctor : T): HYInstanceType <T> { return new ctor () } const p3 = factory (Person ) const d = factory (Dog )
7.4.12.Parameters 用于获取函数的所有参数
用法:Parameters<T>
1 2 3 4 5 6 7 8 type Parameters <T extends (...args : any ) => any > = T extends (...args : infer P) => any ? P : never function test (a: string , b: number ) { return a + b } type ParamsType = Parameters <typeof test>
7.5.自定义工具 7.5.1.ChangeFunctionReturn 用于改变一个函数的返回值类型
用法:ChangeFunctionReturn<F, R>
1 2 3 4 5 6 7 8 9 10 type ChangeFunctionReturn <F extends (...args : any []) => any , R = any > = ( ...args: Parameters<F> ) => Rfunction test (a: string , b: number ) { return a + b } type TestFnType = ChangeFunctionReturn <typeof test, Array <string >>
7.5.2.ChangeFunctionParameters 用于改变一个函数的参数类型
用法:ChangeFunctionParameters<F, P>
1 2 3 4 5 6 7 8 9 10 11 12 13 type ChangeFunctionParameters < F extends (...args : any []) => any , P extends Array <any > > = (...args: P ) => ReturnType <F> function test (a: string , b: number ) { return a + b } type ParametersType = [x : string , y : number , z : number ]type TestFnType = ChangeFunctionParameters <typeof test, ParametersType >
7.5.3.DeepReadonly 用于构造一个Type
下面的所有属性(包括深层Type
)全都设置为只读的类型 ,
用法:DeepReadonly<F>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type DeepReadonly <T extends Record <string | symbol , any >> = { readonly [K in keyof T]: DeepReadonly <T[K]>; } interface IKun { name : string age : number hobbies : { sing : string dance : string rap : string } } type ReadonlyIKun = DeepReadonly <IKun >
8. 知识拓展 6.1.声明文件 当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能
什么是声明语句
假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过 <script>
标签引入 jQuery
,然后就可以使用全局变量 $
或 jQuery
了。
但是在 ts 中,编译器并不知道 $ 或 jQuery 是什么东西
1 2 3 4 5 6 7 8 9 10 jQuery ('#foo' );
这时,我们需要使用 declare var 来定义它的类型
1 2 3 declare var jQuery : (selector: string ) => any ;jQuery ('#foo' );
declare var 并没有真的定义一个变量,只是定义了全局变量 jQuery 的类型,仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:
一般声明文件都会单独写成一个 xxx.d.ts
文件
创建 01_jQuery.d.ts
, 将声明语句定义其中, TS编译器会扫描并加载项目中所有的TS声明文件
1 declare var jQuery : (selector: string ) => any ;
很多的第三方库都定义了对应的声明文件库, 库文件名一般为 @types/xxx
, 可以在 https://www.npmjs.com/package/package
进行搜索
有的第三库在下载时就会自动下载对应的声明文件库(比如: webpack),有的可能需要单独下载(比如jQuery/react)
6.2.内置对象 JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。
内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。
ECMAScript 的内置对象 Boolean Number String Date RegExp Error
1 2 3 4 5 6 7 8 9 let b : Boolean = new Boolean (1 )let n : Number = new Number (true )let s : String = new String ('abc' )let d : Date = new Date ()let r : RegExp = /^1/ let e : Error = new Error ('error message' )b = true
BOM 和 DOM 的内置对象 Window Document HTMLElement DocumentFragment Event NodeList
1 2 3 4 5 6 const div : HTMLElement = document .getElementById ('test' )const divs : NodeList = document .querySelectorAll ('div' )document .addEventListener ('click' , (event: MouseEvent ) => { console .dir (event.target ) }) const fragment : DocumentFragment = document .createDocumentFragment ()