本文最后更新于:19 小时前
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. 索引类型 五、接口(对象的类型) 在 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 ; area : 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这种断言语法
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 2 3 interface SearchFunc { (source : string , subString : string ): boolean ; }
使用接口
1 2 3 4 let mySearch : SearchFunc = function (source: string , subString: string ): boolean { let result = source.search (subString); return result > -1 ; }
书写完整的函数类型,例子:
这里是变量和函数本身都声明了类型
1 2 3 4 5 6 let myAdd : (baseValue: number , increment: number ) => number = function (x: number , y: number ): number { return x + y; };
单独的函数,声明类型是下面这样的
1 2 3 function (x: number , y: number ): number { return x + y; };
上面那个例子,可以只在变量一侧声明类型
1 2 let myAdd : (baseValue: number , increment: number ) => number = function (x, y ) {return x+y}
也可以只在函数一侧声明类型
1 let myAdd = function (x: number , y: number ): number { return x + y; };
上面这两种都是可以的!
传递给一个函数的参数个数必须与函数期望的参数个数一致,不能多、也不能少(可选参数或默认参数除外)
1 2 3 4 5 6 7 8 9 10 function buildName (lastName: string , firstName?: string ){...}function buildName (firstName: string , lastName = "Smith" ) { return firstName + " " + lastName; }let result1 = buildName ("Bob" );let result2 = buildName ("Bob" , undefined ); let result3 = buildName ("Bob" , "Adams" , "Sr." );
如果你并不知道会有多少参数传递进来,可以使用 剩余参数
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修饰符开头
作用是什么 有时,我们不免会引入外部的 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 ; };
@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包的功能
高级类型 索引类型 keyof,示例如下
1 2 3 4 5 6 7 8 9 10 function getValue<T extends object , U extends keyof T>(obj : T, key : U) { return obj[key]; }const a = { name : 'timegogo' , age : 26 , }getValue (a, a.name );