本文最后更新于:4 个月前
TypeScript语法、类型声明文件、高级语法……
TypeScript入门 TypeScript官方文档更适合作为参考文档去查阅语法,并不适合新手去学习。想要快速有效地了解TypeScript,推荐阅读以下两个非常不错的文档!
推荐阅读:
什么是 TypeScript · TypeScript 入门教程 (xcatliu.com)
深入理解 TypeScript | 深入理解 TypeScript (jkchao.github.io)
一、TypeScript是什么
TypeScript 是添加了类型系统的 JavaScript ,适用于任何规模的项目。
TypeScript 是一门静态类型、弱类型的语言。
TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性。
TypeScript 可以编译为 JavaScript,然后运行在浏览器、Node.js 等任何能运行 JavaScript 的环境中。
TypeScript 拥有很多编译选项,类型检查的严格程度由你决定 。
TypeScript 可以和 JavaScript 共存,这意味着 JavaScript 项目能够渐进式的迁移到 TypeScript。
TypeScript 增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构 等能力。
TypeScript 拥有活跃的社区,大多数常用的第三方库都提供了类型声明。
TypeScript 与标准同步发展,符合最新的 ECMAScript 标准(stage 3)。
详情请看:什么是 TypeScript · TypeScript 入门教程 (xcatliu.com)
二、基本类型 这里的基本类型,指在 JavaScript 中也存在的类型
基础类型
1 2 3 4 5 let isDone : boolean = false ; let decLiteral : number = 6 ; let name : string = "bob" ; let u : undefined = undefined ; let n : null = null ;
数组类型,有两种声明方式:
第一种,类型 []
1 let list : number [] = [1 , 2 , 3 ];
第二种, Array<elemType>
数组泛型的形式
1 let list : Array <number > = [1 , 2 , 3 ];
如果数组中存在多种不同类型时,使用 any
1 let list : any [] = [1 , true , "free" ];
三、特殊类型 这里的特殊类型,指 TypeScript 特有的,在 JavaScript 中不存在的类型
1. 任意值any 如果是一个普通类型,在赋值过程中改变类型是不被允许的:但如果是 any
类型,则允许被赋值为任意类型。
1 2 let myFavoriteNumber : any = 'seven' ; myFavoriteNumber = 7 ;
在任意值上可以访问任何属性、调用任何方法,而不会报错(这在其它类型上是不允许的)
1 2 3 let anyThing : any = 'Tom' ;console .log (anyThing.myName ); anyThing.setName ('Jerry' );
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型 :
1 2 3 let something;let something : any ;
如果一个数组中,包含了不同的类型
1 2 let list : any [] = [1 , true , "free" ];
2. 元组Tuple 1 2 3 4 5 let x : [string , number ]; x = ['hello' , 10 ]; x = [10 , 'hello' ];
3. 枚举enum 使用枚举,可以定义一些常量。被枚举的值只能是以下两种:数字、字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 enum Direction { Up = "UP" , Down = "DOWN" , Left = "LEFT" , Right = "RIGHT" , }enum Response { No = 0 , Yes = 1 , }
枚举数字时,具有「自增长」的特点
1 2 3 4 5 6 7 8 9 enum Response { No = 0 , Yes }enum Response { No = 1 , Yes }
以下是枚举的用法
1 2 3 4 5 6 7 8 9 10 11 12 enum Response { No = 0 , Yes = 1 , }function respond (recipient: string , message: Response ): void { }respond ("Princess Caroline" , Response .Yes ) respond ("Princess Caroline" , Response [0 ])
4. never 1 2 3 4 5 6 7 8 9 10 11 function error (message: string ): never { throw new Error (message); }function infiniteLoop ( ): never { while (true ) { } }
四、高级类型 1. 联合类型 联合类型(Union Types)表示取值可以为多种类型中的一种,符号为 |
1 2 3 let myFavoriteNumber : string | number ; myFavoriteNumber = 'seven' ; myFavoriteNumber = 7 ;
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法 :
1 2 3 4 function getLength (something: string | number ): number { return something.length ; }
1 2 3 4 function getString (something: string | number ): string { return something.toString (); }
联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:
1 2 3 4 5 let myFavoriteNumber : string | number ; myFavoriteNumber = 'seven' ;console .log (myFavoriteNumber.length ); myFavoriteNumber = 7 ;console .log (myFavoriteNumber.length );
2. 交叉类型 把多个类型合并为一个类型,,符号为 &
1 2 3 4 5 6 7 8 9 10 11 12 interface Person { name : string ; }interface Student { grade : number ; }let obj : Person & Student = { name : 'LiMing' , grade : 2 , }
3. 索引类型 示例如下
1 2 3 4 5 6 7 8 9 10 function getValue<T extends object , U extends keyof T>(obj : T, key : U): T[U] { return obj[key]; }const a = { name : 'timegogo' , age : 26 , }getValue (a, 'name' );
keyof
,索引类型查询操作符 。 对于任何类型 T
, keyof T
的结果为 T
上已知的公共属性名的联合。
T[U]
, 索引访问操作符
另外一种实践中很常见的用法:允许额外的类型
1 2 3 interface Map <T> { [key : string ]: T; }
1 let x : { foo : number , [x : string ]: any };
五、接口(对象的类型) 在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。接口一般首字母大写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface Person { name : string ; age : number ; }let tom : Person = { name : 'Tom' };let tom : Person = { name : 'Tom' , age : 25 , gender : 'male' };
后面带个?
,表示属性可选
1 2 3 4 interface SquareConfig { color?: string ; width?: number ; }
前面加一个readonly
,表示只读属性
1 2 3 4 interface Point { readonly x : number ; readonly y : number ; }
额外的属性检查 ,如果定义对象时传入了额外的(没有定义在接口中的)属性,会报错
1 2 3 4 5 6 7 8 9 10 interface SquareConfig { color?: string ; width?: number ; }function createSquare (config: SquareConfig ): { color : string ; width : number } { }let mySquare = createSquare ({ colour : "red" , width : 100 });
六、类型推论 & 断言 & 兼容 1. 类型推论 如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型
1 2 3 4 5 let myFavoriteNumber = 'seven' ; myFavoriteNumber = 7 ;
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any
类型而完全不被类型检查 :
1 2 3 let myFavoriteNumber; myFavoriteNumber = 'seven' ; myFavoriteNumber = 7 ;
2. 类型断言 类型断言好比其它语言里的类型转换,有两种写法:“尖括号” 或者 as
1 2 3 let strLength : number = (<string >someValue).length ; let strLength : number = (someValue as string ).length
注意:在JSX
中只能使用as
这种断言语法!
双重断言 ,毫无根据的断言又是会被TS拒绝,并报错。这是可以通过先断言成any
或者unknown
,再进一步断言成其它类型
1 2 3 function handler (event: Event ) { const element = (event as any ) as HTMLElement ; }
3. 类型兼容 原始类型 和 对象类型 的类型兼容,“字段多” 赋值给 “字段少” 是正确的
1 2 3 4 5 6 7 8 interface Named { name : string ; }let x : Named ;let y = { name : 'Alice' , location : 'Seattle' }; x = y; y = x;
函数 的类型兼容,
从参数 的角度,“参数少的” 赋值给 “参数多的” 是正确的
1 2 3 4 5 let x = (a: number ) => 0 ;let y = (b: number , s: string ) => 0 ; y = x; x = y;
从返回值 的角度,”返回值多的“ 赋值给 ”返回值少的“ 是正确的
1 2 3 4 5 let x = ( ) => ({name : 'Alice' });let y = ( ) => ({name : 'Alice' , location : 'Seattle' }); x = y; y = x;
三、函数类型声明 1. 内联声明 1 2 3 function fn (bar: string , foo: number ): boolean { ... }
2. 接口声明 可以使用:
冒号
1 2 3 4 5 6 7 interface SearchFunc { (source : string , subString : string ): boolean ; }type SearchFunc = { (source : string , subString : string ): boolean ; }
也可以使用=>
箭头
1 2 type SearchFunc = (source: string , subString: string ) => boolean ;
下面是对函数声明的使用/消费
1 2 3 let mySearch : SearchFunc = function (source, subString ){ ... }
3. 函数重载声明 多次声明函数头,实现函数重载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function padding (all: number );function padding (topAndBottom: number , leftAndRight: number );function padding (top: number , right: number , bottom: number , left: number );function padding (a: number , b?: number , c?: number , d?: number ) { if (b === undefined && c === undefined && d === undefined ) { b = c = d = a; } else if (c === undefined && d === undefined ) { c = a; d = b; } return { top : a, right : b, bottom : c, left : d }; }
上三行是函数声明,第四行及往下是函数实现。
使用接口进行重载声明
1 2 3 4 5 6 7 8 9 type CompareFunc = { (left : string , right : string ) : boolean ; (left : number , right : number ) : boolean ; }interface CompareFunc { (left : string , right : string ): boolean ; (left : number , right : number ): boolean ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface Overloaded { (foo : string ): string ; (foo : number ): number ; }function stringOrNumber (foo: number ): number ;function stringOrNumber (foo: string ): string ;function stringOrNumber (foo: any ): any { if (typeof foo === 'number' ) { return foo * foo; } else if (typeof foo === 'string' ) { return `hello ${foo} ` ; } }const overloaded : Overloaded = stringOrNumber;
4. 剩余参数声明 1 function buildName (firstName: string , ...restOfName: string [] ) {...}
四、泛型 使用泛型
来创建可重用的组件,一个组件可以支持多种类型的数据.
比如有一个需求:函数接受、返回同一种类型,但是可以是任意类型,实现如下:
1 2 3 4 function identity<T>(arg : T): T { return arg; }
上面定义了泛型函数,下面有两种方法使用
1 2 3 4 5 let output = identity<string >("myString" )let output = identity ("myString" )
泛型接口
把泛型参数T,作为接口的一个参数
1 2 3 4 5 6 7 interface GenericIdentityFn <T> { (arg : T): T; }function identity<T>(arg : T): T { return tag; }let myIdentity : GenericIdentityFn <number > = indentity;
对比一下非泛型
1 2 3 4 5 6 7 8 interface GenericIdentityFn { (arg : number ): number ; }function identity (arg: number ){ return arg }let myIdentity : GenericIdentityFn = indentity;
泛型约束
假设泛型T,只能传递 string 和 number 两种类型,那么可以使用 <T extends xx>
的方式进行约束,示例如下
1 2 3 4 type Params = string | number ;class Stack <T extends Params > { ... }
六、tsconfig.json 如果一个目录下存在一个tsconfig.json
文件,那么它意味着这个目录是TypeScript项目的根目录
何时使用
不带任何输入文件的情况下调用tsc
,编译器会从当前目录开始去查找tsconfig.json
文件,逐级向上搜索父目录。
当命令行上指定了输入文件时,tsconfig.json
文件会被忽略
文件结构解读 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 { "compilerOptions" : { "declaration" : true , "declarationDir" : "dist/type" , "outDir" : "dist" , "module" : "commonjs" , "noImplicitAny" : true , "preserveConstEnums" : true , "removeComments" : true , "sourceMap" : true , "typeRoots" : ["./typings" ], "types" : ["node" , "lodash" , "express" ] }, "files" : [ "core.ts" , ... ], "include" : [ "src/**/*" ], "exclude" : [ "node_modules" , "**/*.spec.ts" ], "extends" : "./config/base" }
exclude
可以过滤掉include
指定的范围,但是无法过滤掉files
指定的范围
注意:每个配置项都具有默认值,并不是所有项目都需要手动配置
如果"files"
和"include"
都没有被指定,编译器默认包含当前目录和子目录下所有的TypeScript文件(.ts
, .d.ts
和 .tsx
)
七、.d.ts文件
.d.ts
,TypeScript的类型声明文件
在*.d.ts
文件中的顶级声明必须以declare
或export
修饰符开头
1. 作用是什么 有时,我们不免会引入外部的 JS库,这时TS就对引入的JS文件里变量的具体类型不明确了,为了告诉TS变量的类型,因此就有了.d.ts
很多主流的库都是 JS编写的,并不支持类型系统。这个时候你不能用TS重写主流的库,这个时候我们只需要编写仅包含类型注释的 d.ts 文件,然后从您的 TS 代码中,可以在仍然使用纯 JS 库的同时,获得静态类型检查的 TS 优势。
以下是一个.ts
文件
1 2 3 4 5 6 7 8 9 interface Person { firstName : string ; lastName : string ; }function greeter (person: Person ) { return "Hello, " + person.firstName + " " + person.lastName ; }let user = { firstName : "Jane" , lastName : "User" };document .body .innerHTML = greeter (user);
下面是tsc为上面那个文件生成的.d.ts
文件
1 2 3 4 5 6 7 8 9 interface Person { firstName : string ; lastName : string ; }declare function greeter (person: Person ): string ;declare let user : { firstName : string ; lastName : string ; };
2. @types/*文件 npm包的声明文件
多数情况下,类型声明包的名字总是与它们在`npm`上的包的名字相同,但是有`@types/`前缀
与 tsconfig.json 文件中 compilerOptions 中的 typeRoots
和 types
搭配使用。
默认情况下所有可见的@types
包,会在编译过程中包含进来,例如:./node_modules/@types/
、../node_modules/@types/
和../../node_modules/@types/
等等。
但是,如果指定了typeRoots
或者types
,那么只有typeRoots
目录下的包才会被引入,或者被types
指定的包才会被引入。
设置"types": []
会禁用自动引入@types
包的功能
3. shims-vue.d.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 declare module '*.vue' { import Vue from 'vue' export default Vue }declare module 'vue-echarts' 作者:deepCode 链接:https : 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
4. shime-jsx.d.ts 八、typescript模块