TypeScript 基础
参考:https://ts.xcatliu.com/
原始数据类型
布尔值、数值、字符串、null、undefined、以及 ES6 中的新类型 Symbol
布尔值
1 | let isDone: boolean = false; |
注意,使用构造函数Boolean
创造的对象不是布尔值
1 | let createdByNewBoolean: boolean = new Boolean(1); |
编译报错new Boolean()
返回的是一个Boolean
对象
直接调用Boolean()
也可以返回一个boolean
类型,将参数转化为 Boolean 值。
数值
number
1 | let decLiteral: number = 6; |
编译结果:
二进制和八进制会编译成十进制
1 | var decLiteral = 6; |
字符串
string
1 | let myName: string = "Tom"; |
编译结果:
1 | var myName = "Tom"; |
元组 Tuple
元组类型是用固定数量的元素组成的数组,元素的类型是已知的,但不一定是相同的
1 | let x: [string, number]; |
通过下标可以访问元素,访问不存在的下标会报错。
1 | console.log(x[0]); |
枚举 Enum
枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射。
值是只读的,无法修改
1 | enum Color { |
可以设置其中一个成员的值,后面的值随之递增
1 | //枚举 |
也可以同时设置所有的值
1 | enum Color { |
如果一个枚举的值为 string,后面的值都要设置
1 |
空值
JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以使用 void 表示没有任何返回值的函数;
1 | function alterName(): void { |
void 类 U 型的变量只能赋值 undefined 和 null
1 | let unusable:void = undefined; |
Null 和 Undefined
使用null
和undefined
来定义原始数据类型:
1 | let u: undefined = undefined; |
与void
的区别,undefined
和null
是所有类型的子类型。也就是说undefined
类型的变量,可以赋值给number
类型的变量.
当指定了–strictNullChecks 标记,null 和 undefined 只能赋值给 void 和他们各自。
1 | let num: number = undefined; |
任意值
what
Any 用来表示允许赋值为任意类型。
1 | let myFavoriteNumber: string = "seven"; |
报错:hello.ts:36:1 - error TS2322: Type ‘7’ is not assignable to type ‘string’.
如果类型为 any,就被允许赋值为任意值
1 | let myFavoriteNumber1: any = "seven"; |
任意值的属性和方法
在任意值上访问任何属性都是允许的
1 | let anyThing: any = "hello"; |
也允许调用任何方法
1 | let anyThing: any = "hello"; |
声明一个变量为任意值后,对它的任何操作,返回的内容的类型都是任意值。
未声明类型的变量
如果在声明的时候未指定其类型,就会被识别为任意值类型。
1 | let something; |
Nerver
what
表示那些用不存在的值的类型
- 总是会抛出异常或根本不会有返回值的函数表达式或箭头函数表达式的返回值类型。
- 变量被永不为真的类型所保护时,也可以是 never 类型
1 | // 有无法到达的终点 |
- never 是任何类型的子类型,并且可以赋值给任何类型
- 除了 never 类型本身以外没有类型可以赋值给 never 类型
- 在一个没有返回值标注的函数表达式或箭头函数中, 如果函数没有 return 语句, 或者仅有表达式类型为 never 的 return 语句, 并且函数的终止点无法被执行到 (按照控制流分析), 则推导出的函数返回值类型是 never.
- 在一个明确指定了 never 返回值类型的函数中, 所有 return 语句 (如果有) 表达式的值必须为 never 类型, 且函数不应能执行到终止点.
Object
非原始类型,除了number
,string
,boolean
,symbol
,null
,undefined
之外的类型。
使用Object
类型就可以更好的表示像Object.create
这样的 API。
1 | declare function create(o: object | null): void; |
类型检查机制
类型推论
如果没有明确的指定类型,TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。
推论发生在初始化变量和成员
1 | let myFavoriteNumber = "seven"; |
等价于
1 | let myFavoriteNumber:string = "seven"; |
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断为any
类型,而类型完全不被类型检查
1 | let myFavoriteNumber1; |
最佳通用类型
计算通用类型算法会考虑所有的候选类型,并给出一个兼容所有候选类型的类型。
1 | let x =[0,1,null] |
考虑所有元素的类型,推论 x 的类型为(number|null)[]
。
如果没有找到最佳通用类型,类型判断结果为联合数组类型
注:需要在 tsconfig.js 配置strictNullChecks": false
,将null
,undefined
视为其他类型的子类型,x 被推断为number[]
1 | let zoo =[new Rhino(), new Elephant(), new Snake()] |
没法直接推断 zoo 的类型为 Animal[]类型,只能联合数组类型[Rhino|Elephant|Snake]
上下文归类
1 | window.onmousedown = function(mouseEvent) { |
TypeScript 类型检查器会使用 Window.onmousedown 函数的类型来推断右边函数表达式的类型。 所以它能够推断出 mouseEvent 参数的类型中包含了 button 属性而不包含 kangaroo 属性。
可能是我编写测试代码环境有问题,编译的时候没报错
上下文类型也会作为最佳通用类型的候选类型
1 | function createZoo(): Animal[]{ |
这个例子里,最佳通用类型有 4 个候选者:Animal,Rhino,Elephant 和 Snake。
类型断言
Type Assertion
用来手动指定一个值的类型
语法
1 | <类型>值 |
在 tsx 语法(React 的 jsx 语法的 ts 版中必须用后一种
例子:将一个联合类型的变量指定为一个更加具体的类型
1 | function getLength(something: string | number): number { |
有时候我们确实需要在还不确定类型的时候访问其中一个类型的属性或方法。
1 | function getLength(something: string | number): number { |
此时可以使用类型断言,将 something 断言成 string:
1 | function getLength(something: string | number): number { |
类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的
1 | function toBoolean(something: string | number): boolean { |
类型兼容性
定义
类型 Y 可以被赋值给类型 X,则称类型 X 兼容类型 Y
结构之间兼容:成员少的兼容成员多的
函数之间兼容:参数多的兼容参数少的
1 | let s: string = 'a' |
接口兼容
1 | interface X{ |
所以 X 兼容 Y,Y 不兼容 X
函数兼容性
1)参数个数
1 | type Handler = (a: number, b: number) => void |
可选参数
1 |
|
2)参数类型
1 | let handler3 = (a: string) => { } |
对象参数
1 | interface Point3D{ |
3)返回值类型
1 | let f = () => ({ name: 'Alice' } ); |
重载
1 | function overload(a: number, b: number): number; |
枚举类型兼容性
1 | enum Fruit{ Apple, Banana } |
类兼容性
构造函数和静态属性不参与兼容比较
1 | class A{ |
如果含有私有属性,不兼容其他类。兼容其子类
1 | class C extends A{ |
泛型兼容性
1 | interface Empty<T>{ |
泛型函数,参数多的兼容参数少的
1 | let log1 = <T>(x: T): T => { |
类型保护
TS 能够在特定的区块中保证变量属于某种确定的类型
可以在此区块中放心的引用此类型的属性或者调用此类型的方法
1 | enum Type{ Strong, Week } |
instanceof
1 | function getLanguage(type: Type) { |
in
1 | enum Type{ Strong, Week } |
typeof
1 | function fun(x:string|number){ |
类型保护函数
1 | function isJava(lang:Java|JavaScript):lang is Java{ |
联合类型
Union Types 表示取值可以为多种类型中的一种
1 | let a: string | number; |
分隔符:|
访问联合类型的属性或方法
TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,只能访问此联合类型的所有类型里的共有属性或方法
;
1 | function getLength(st: string | number): number { |
length 不是 string 和 number 的共有属性所以会报错。
1 | function getLength(st: string | number): string { |
toString 是 String 和 Number 的共有属性。
联合类型的变量在被赋值的时候,会根据类型推论的规则推出一个类型:
1 | let b: string | number; |
第二行b
被推断成 string,访问它的length
属性不会报错。第四行b
被推断为number
,访问length
报错。
对象的类型–接口
面向对象语言中,接口(Interfaces)是一个很重要的概念,是对行为的抽象,具体如何行动需要由类去实现。
TypeScript 中的接口可用于对类的一部分行为进行抽象,也常用于对对象的形状进行描述。
1 | interface Person { |
接口一般首字母大写。有的编程语言中会建议接口的名称加上 I 前缀。
定义的变量比接口少了一些属性是不允许的。
1 | let tom: Person = { |
多了一些属性也是不允许的
1 | let tom: Person = { |
赋值的时候,变量的形状必须和接口的形状保持一致。
可选属性
不需要完全匹配
1 | interface Person { |
但是非可选属性必须存在,也不允许添加未定义属性。
任意属性
希望接口允许有任意属性。
1 | interface Person { |
注意,一旦定义了任意属性,那么确定属性和可选属性的类型必须是它的类型子集
1 | interface Person { |
任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错
只读属性
对象的属性只能在对象创建的时候被赋值,可以使用 readonly 定义只读属性
1 | let tom: Person = { |
注意:只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
1 | interface Person { |
数组 Array
- 元素类型后面加上[]
1 | let list: number[] = [1, 2, 3]; |
编译为
1 | var list = [1, 2, 3]; |
- 使用数组泛型,Array<元素类型>
1 | let list:Array<number> = [1,2,3] |
- 用接口表示数组
1 | interface NumberArray { |
NumberArray
表示:只要索引的类型是数字,值的类型必须为数字
虽然接口可以用来描述数组,但是这种结果太复杂,一般不使用。
- 类数组
1 | function sum() { |
arguments 实际是一个类数组,不能用普通的数组的方式来描述。而用接口:
1 | function sum() { |
约定索引的类型和值的类型必须是数字外,也约束了 length 和 callee 两个属性。
事实上常用的类数组都有自己的接口定义,如IArguments
,NodeList
,HTMLCollection
等
1 | function sum() { |
其中 IArguments 是 TypeScript 中定义好的类型,它实际上就是:
1 | interface IArguments{ |
- any 在数组中的应用
一个比较常见的做法是,用 any 表示数组中允许出现任意类型
1 | let list: any[] = ["string", 24, { websites: "http://www.ss.com" }]; |
函数的类型
函数声明
1 | //函数声明 |
TypeScript 中对函数的输入输出进行约束,其中函数声明的类型定义较简单
1 | function sum(x: number, y: number): number { |
注意,输入多余或少于要求的参数,是不被允许的
1 | sum(1) |
函数表达式
1 | let mySum = function(x: number, y: number): number { |
上述定义,可以编译通过,但是只对等号右边的匿名函数进行类型定义,而等号左边的mySum
,是通过赋值操作进行类型推论而推断出来的。
如果需要我们手动给 mySum 添加类型,则应该是这样:
1 | let mySum:(x:number,y:number) =>number = function(x: number, y: number): number { |
不要混淆 TypScript 中=>
和 ES6 中的=>
在 TypeScript 的类型定义中,=>
用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
用接口定义函数的类型
1 | interface SearchFun { |
可选参数
?
定义可选参数
1 | function buildName(firstName: string, lastName?: string) { |
注意,可选参数必须在必需产生后面,可选参数后面不能出现必需参数
1 | function buildName(lastName?: string, firstName: string) { |
参数默认值
TypeScript 会将添加了默认值的参数识别为可选参数
1 | function buildName(firstName: string, lastName: string = "Cat") { |
此时不受可选参数必须在参数后面的限制了
1 | function buildName(lastName: string = "Cat", firstName: string) { |
剩余参数
...rest
获取剩余参数
1 | function push(array: any[], ...items: any[]) { |
注意,rest 参数只能是最后一个参数
重载
允许一个函数接受不同数量或类型的参数时,作出不同的处理。
实现一个函数reverse
。
1 | function reverse(x: number | string): number | string { |
可以使用重载定义多个 reverse 的函数类型
1 | function reverse(x: number): number; |
TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
声明文件
使用第三方库的时候,需要引用其声明文件,才能获得对应的代码补全、接口提示等功能。
声明语句
declare var
声明全局变量declare function
声明全局方法declare class
声明全局类declare enum
声明全局枚举类型declare namespace
声明(含子属性的)全局对象interface 和 type
声明全局类型export
导出变量export namespace
导出(含有子属性的)对象export default
ES6 默认导出export =
commonjs 导出模块export as namespace
UMD 库声明全局变量declare global
扩展全局变量declare module
扩展模块///<reference/>
三斜线指令
例如,ts 无法识别用 script 标签引入的 Jquery,需要声明
1 | declare var jQuery:(selector:string) => any |
编译为
1 | jQuery('#foo') |
将声明语句放到一个单独文件(jQuery.d.ts)中
1 | //src/jQuery.d.ts |
1 | // src/index.ts |
注意,声明文件必须以.d.ts
结尾
一般 ts 会解析项目中所有的*.ts
文件,当然包括.d.ts
结尾的文件,所以将jQuery.d.ts
放到项目中,其他所有*.ts
文件都可以获得jQuery
的类型定义
项目目录
1 | /path/to/project |
假如仍然无法解析,可以检查一下tsconfig.json
中的files
,include
和exclude
配置,确保其包含了jQuery.d.ts
文件。
第三方声明文件
jQuery 声明文件可以直接下载,jQuery in DefinitelyTyped)使用。
更推荐使用@types
统一管理第三方库的声明文件。
1 | npm install @types/jquery --save-dev |
可以使用TypeSearch搜索你要安装的库
书写声明文件
第三方库没有提供声明文件时,需要自己书写声明文件。
不同的场景下,声明文件的内容和使用方式不同
- 全局变量:通过
<script>
标签引入第三方库。注入全局变量; - npm 包:通过
import foo from 'foo
导入,符合 ES6 规范; - UDM 库:既可以通过
<script>
标签引入,又可以通过import
引入; - 直接扩展全局变量:通过
<script>
标签引入后,改变一个全局变量结构; - 在 npm 包或 UMD 库中扩展全局变量:引用
npm包
或UMD库
后,改变一个全局变量结构; - 模块插件:通过
<script>
引入后,改变另一个模块的结构。
全局变量
之前举的例子就是通过<script>
标签引入 jQuery,注入全局变量 \$ 和 jQuery。
如果是以npm install @types/xxx --save-dev
安装的第三方库,就不需要配置。如果是将声明文件直接存放在当前项目中,则建议和其他源码一起放在src
目录下(或对应源码目录下)
1 | /path/to/project |
全局变量的声明文件语法:
declare var
声明全局变量declare function
声明全局方法declare class
声明全局类declare enum
声明全局枚举类型declare namespace
声明(含子属性的)全局对象interface 和 type
声明全局类型
declare var
还有declare let
和declare const
,与直接使用let
,const
没区别:
// src/jQuery.d.ts
1 | declare let jQuery: (selector: string) => any; |
// src/index.ts
1 | jQuery('#foo'); |
let 声明的变量可以更改,const 更改的不能修改
// src/jQuery.d.ts
1 | declare const jQuery: (selector: string) => any; |
// src/index.ts
1 | jQuery = function(selector) { |
注意:声明语句中只能定义类型,切勿在声明语句中定义具体的实现
1 | declare const jQuery = function(selector) { |
declare function
用来定义全局函数的类型,jQuery 其实就是一个函数,也可以使用function
来定义
1 | declare function jQuery(selector:string):any |
在函数类型的声明语句中,函数重载也支持:
//src/jQuery.d.ts
1 | declare function jQuery(selector:string):any |
//src/index.ts
1 | jQuery('#foo'); |
declare class
当全局变量是一个类时,使用declare class
定义它的类型
1 | //src/Animal.d.ts |
同样的,declare class 语句也只能用来定义类型,不能用来定义具体的实现,比如定义 sayHi 方法的具体实现则会报错:
1 | // src/Animal.d.ts |
declare enum
使用declare enum
定义的枚举类型也称作外部枚举 (Ambient Enums)
仅用于定义类型,而不是具体的值。
declare namespace
命名空间
早前 ts 提供了一种模块化方案,使用module
关键字表示内部模块,后来为了兼容 ES6 就把module
替换为namespcae
,现在已经不建议使用 ts 的namespace
。
declare namespace 还是常用的,用来表示全局变量是一个对象,包含很多子属性。
例如 jQuery 对象中的 ajax 方法
src/jQuery.d.ts
1 | declare namespace jQuery { |
src/index.ts
1 | jQuery.ajax('/api/get') |
注意,在declare namespace
内部,直接使用 function ajax
声明函数,而不是使用 declare function ajax
。类似里面还可以使用 const
,class
,enum
src/jQuery.d.ts
1 | declare namespace jQuery { |
src/hello.ts
1 | const e = new jQuery.Event(); |
嵌套的命名空间
如果对象拥有深层的层级,则需要用嵌套的namespace
来声深层属性的类型
src/jQuery.d.ts
1 | declare namespace jQuery { |
src/index.ts
1 | jQuery.fn.extend({ |
假如jQuery
下面仅有fn
这一属性(没有ajax
等其他属性或方法),则可以不需要嵌套namespace
;
src/jQuery.d.ts
1 | declare namespace jQuery.fn { |
src/index.ts
1 | jQuery.fn.extend({ |
interface 和 type
直接使用interface
或type
来声明一个全局的接口或类型。
src/jQuery.d.ts
1 | interface AjaxSettings { |
其他文件也可以使用这个接口或类型
1 | let settings: AjaxSettings = { |
防止命名冲突
暴露在最外层的interface
或type
会作为全局类型作用于整个项目,应该尽量减少全局变量或全局类型的数量,故最好将他们放到namespace
下
1 | declare namespace jQuery { |
注意,使用interface
的时候,也应该加上jQuery
前缀
src/jQuery.d.ts
1 | declare namespace jQuery { |
src/index.ts
1 | let settings: jQuery.AjaxSettings = { |
声明合并
假如 jQuery 既是一个函数,可以直接被调用jQuery("#foo")
,又是一个对象拥有子属性jQuery.ajax()
,那么我们可以组合多个声明语句,他们不冲突的合并起来。
src/jQuery.d.ts
1 | declare function jQuery(selector: string): any; |
src/hello.js
1 | jQuery("#foo"); |
npm 包
ES6:import foo from 'foo'
为 npm 包创建声明文件之前,需要先看它的声明文件是否存在,一般来说,npm 包的声明文件可以存在于两个地方:
- 与该 npm 包绑定在一起。判断依据
package.json
中有types
字段,或者有一个index.d.ts
声明文件。这种模式不需要额外安装其他包,最为推荐,所以以后我们自己创建 npm 包的时候,最好将声明文件与 npm 包绑定在一起。 - 发布到
@types
里。我们只需要尝试安装一下对应的@types
包就知道是否存在该声明文件,安装命令是npm install @types/foo --save-dev
。这种模式一般是由于 npm 包的维护者没有提供声明文件,所以只能由其他人将声明文件发布到@types
里
假如以上两种方式都没有找到对应的声明文件,那么就需要自己写声明文件,由于是通过import
语句导入模块,所以声明文件存放的位置也有所约束,一般有两种方案:
- 创建一个
node_module/@types/foo/index.d.ts
文件,存放foo
模块的声明文件。这种方式不需要额外配置,但是node_modules
目录不稳定,代码也没有被保存到仓库,无法回溯版本,有被删除的风险,故不太建议用这种方案,一般只做临时测试。 - 创建一个
types
目录,专门用来管理自己写的声明文件,将foo
的声明文件放到types/foo/index.d.ts
中,这种方式需要配置下tsconfig.json
中的paths
和baseUrl
字段
目录结构:
1 | /path/to/project |
tsconfig.json 内容:
1 | { |
如此配置之后,通过 import 导入 foo 的时候,也会去 types 目录下寻找对应的模块的声明文件了。
注意,module
配置可以有多种选项,不同的选项影响模块的导入导出模式,commonjs
是最常用的选项
npm 包的声明文件主要有以下几种语法:
export
导出变量export namespace
导出(含子属性的)对象export default
ES6 默认导出export=
commonjs 导出模块
export
npm 包的声明文件与全局变量的声明文件有很大的区别,使用declare
不再会声明一个全局变量,而只会在当前文件中声明一个局部变量。只有在声明文件中使用export
导出,然后在使用import
导入,才会应用到这些类型声明。export
的语法与普通 ts 语法类似,区别在于声明文件中禁止定义具体的实现:
src/foo/index.d.ts
1 | export const name: string; |
对应的导入和使用模块应该这样使用
src/index.ts
1 | import { name, getName, Animal, Directions, Options } from "foo"; |
混用 declare 和 export
先使用declare
声明多个变量,然后再使用export
一次性导出。
1 | declare const name: string; |
注意,与全局变量声明文件类似,interface
前不需要declare
export namespace
导出一个拥有子属性的对象
types/foo/index.d.ts
1 | export namespace foo { |
src/index.ts
1 | import {foo} from 'foo'; |
export default
导出默认值
types/foo/index.d.ts
1 | export default function foo():string |
src/index.ts
1 | import foo from 'foo' |
注意,只有function
,class
,interface
可以直接默认导出,其他变量需要先定义再默认导出
1 | export default Directions; |
默认导出一般放在声明文件的最前面
export =
在commonjs
规范中,导出模块
1 | //整体导出 |
导入方式
const ... = require
1 | //整体导入 |
- ‘import … from ‘
整体导入import * as ... from
1 | //整体导入 |
import ... require
,ts 官方推荐
1 | //整体导入 |
对于这种使用 commonjs 规范的库,假如为它写类型声明文件的话,就需要使用export =
这种语法
1 | export = foo; |
上例使用export =
之后,就不能使用单个导出export {bar}
。通过声明合并,使用declare namespace foo
来将bar
合并到foo
export =
不仅可以用在声明文件中,也可以用在普通的 ts 文件中。实际上,import ... require 和
export =
都是 ts 为了兼容 AMD 规范和 commonjs 规范而创立的新语法,由于并不常用也不推荐使用。
不推荐使用export =
,除非第三方使用 commonjs 规范。推荐使用 ES6 标准的export default
和export
。
UMD 库
既可以通过<script>
标签引入,又可以通过import
导入的库,称为 UMD 库。相比于 npm 包的类型声明文件,我们需要额外声明一个全局变量,为了实现这种方式,ts 提供一个新语法export as namespace
。
export as namespace
一般使用export as namespace
时,都先有了 npm 包的声明文件,再基于它添加一条export as namespace
语句,即可将声明好的一个变量声明为全局变量。
1 | export as namespace foo; |
也可以与export default
一起使用
1 | export as namespace foo; |
直接扩展全局变量
有的第三方库扩展了一个全局变量,可是全局变量的类型却没有相应的更新过来。就会导致 ts 编译出错
扩展String
类型:
1 | interface String{ |
通过声明合并,使用interface String
即可给String
添加属性或方法
也可以使用declare namespace
给已有的命名空间添加类型声明
1 | // types/jquery-plugin/index.d.ts |
在 npm 包或 UMD 库中扩展全局变量
如之前所说,对于一个 npm 包或者 UMD 库的声明文件,只有export
导出的类型声明才能被导入。所以对于 npm 包或 UMD 库,如果导入此库之后会扩展全局变量,则需要使用另一种语法在声明文件中扩展全局变量的类型,那就是 declare global
。
declare global
可以在 npm 包或者 UMD 库的声明文件中扩展全局变量的类型。
types/foo/index.d.ts
1 | declare global { |
src/index.ts
1 | 'bar'.prependHello(); |
注意,此声明文件不需要导出任何东西,但是仍需要导出一个空对象,用来告诉编译器这是一个模块的声明文件,而不是一个全局变量的声明文件
模块插件
通过import
导入一个模块插件,可以改变另一个原有模块的结构。此时如果原有模块已经有了类型声明文件,而插件模块没有类型声明文件,导致类型不完整。
declare module
如果需要扩展原有模块的话,需要在类型声明文件中引用原有模块,再使用declare module
扩展原有模块。
types/moment-plugin/index.d.ts
1 | |
src/index.ts
1 | import * as moment from 'moment'; |
一次性声明多个模块类型
types/foo-bar.d.ts
1 | declare module "foo" { |
src/index.ts
1 | import { Foo } from 'foo'; |
声明文件中的依赖
一个声明文件依赖另一个声明文件中的类型,declare module
例子中,导入moment
,并使用了moment.CalendarKey
类型:
1 | // types/moment-plugin/index.d.ts |
除了可以在声明文件中通过import
导入另一个声明文件,还可以使用三斜线指令导入
三斜线指令
现在推荐使用 ES6 来实现模块之间的依赖,不过声明模块之间的依赖关系还需要使用三斜线指令
当我们在书写一个全局变量的声明文件时
在全局变量的声明文件中,不允许出现import
,export
关键字,一旦出现,文件就会被视为一个 npm 包或 UMD 库,如果全局变量声明文件需要引用另一个库,就必须使用三斜线指令1
2/// <reference types="jquery" />
declare function foo(options: JQuery.AjaxSettings): string;语法
///
后面使用 xml 格式添加对jquery
类型的依赖,这样就可以在声明文件中使用JQuery.AjaxSettings
类型
注意,三斜线指令必须放在文件的最顶端,三斜线指令的最前面值允许出现单行或多行注释当我们需要依赖一个全局变量的声明文件时
types/node-plugin/index.d.ts
1 | ///<reference types='node'/> |
src/index.ts
1 | import {foo } from 'node-plugin'; |
由于引入的 node 中的类型都是全局变量的类型,它们是没有办法通过 import 来导入的,所以这种场景下也只能通过三斜线指令来引入了。
拆分声明文件
当全局声明文件太大时,可以拆分为多个文件,然后在一个入口文件中将他们一一引入,提高代码的可维护性。jquery
的声明文件:
1 | /// <reference types="sizzle" /> |
其中用到了types
和path
两种不同参数。
- types:用于声明对另一个库的依赖,
- path:用于声明对另一个文件的依赖
自动生成声明文件
如果库的源码本身就由 ts 写的,那么在使用 tsc 脚本将 ts 编译为 js 的时候,添加declaration
选项。
1 | { |
outDir
,将 ts 文件编译后的结果输出的到 lib 目录下declaration
,执行编译的时候,ts 文件不仅生成.js
文件同时自动生成.d.ts
声明文件,也会输出到lib
目录下
其他配置declarationDir
,设置生成.d.ts
文件的目录declarationMap
,对每个.d.ts
文件,生成对应的.d.ts.map
文件emitDeclarationOnly
,仅生成’.d.ts’文件,不生成.js
文件
内置对象
ECMAScript 的内置对象
根据 js 的内置对象定义 ts 变量
1 | let aa: Boolean = new Boolean(1); |
DOM 和 BOM 的内置对象
1 | let body: HTMLElement = document.body; |
TypeScript 核心库的定义文件
定义了所有了浏览器环境需要用到的类型,并且预置在 TypeScript 中
1 | Math.pow(10, "2"); |
Math.pow 必须接受两个 number 类型的参数。Math.pow 的类型定义如下
1 | interface Math { |
DOM 的例子
1 | document.addEventListener('click', function(e) { |
addEventListener 方法是在 TypeScript 核心库中定义的:
1 | interface Document extends Node, GlobalEventHandlers, NodeSelector, DocumentEvent { |
注意:TypeScript 核心库的定义不包含 Node.js 部分
用 TypeScript 写 Node.js
node.js 不是内置对象的一部分,如果要用 TypeScript 写 Node.js,则需要引入第三方声明文件:
1 | npm install @types/node --save-dev |