TypeScirpt类型声明完全指南
#
**类型声明(Type Declaration)
或者
类型定义(Type Definition)**文件是一个以
.d.ts
作为文件后缀名的TypeScript文件。它们与普通
.ts
文件有什么区别呢?有什么特征呢?接下来,一起深入了解下。
一.
.d.ts
文件
#
类型声明文件的后缀名为
.d.ts
,文件中只包含与类型相关的代码,不包含逻辑代码,它们的作用旨在为开发者提供类型信息,所以它们只在开发阶段起作用。
在
TypeScript编译
文章中介绍过,typescript编译后会将类型信息移除,类型信息仅在开发阶段起作用。
类型声明,即类型的声明,例如
interface
、
function
和
class
,下面是一个
interface
声明。
interface Person {
name: string;
age: number;
const xm: Person = {
name: "XiaoMing",
age: 8
二.全局声明
#
**全局声明(global declaration)**在任何TypeScript项目或者TypeScript代码片段中都会起作用的。例如当你写下
const p = new Promise();
时,TypeScript编译器不会去编译你的代码,因为
const p = new Promise();
语法错误了,如果你使用的是vscode IDE,那么vscode向下图中那样提示你(个人写代码,习惯用白色背景,vscode默认的背景色是黑色,虽然主题色不同,但是提示是相同的)。
vscode就是根据全局声明检测到你的语法错误的。每个TypeScript程序都可以使用全局声明,无需显示的用
import
语句导入。
从IDE的提示中,可以看到
Promise
类型是定义在
lib.es2015.promise.d.ts
文件中的,由TypeScript提供,TypeScript提供的很多这样的声明文件,被称作
标准库
(这些声明文件会随TypeScript一起安装)。
使用
lib
编译选项,你可以控制哪些标准库被引入到当前项目。也可以设置
noLib
为
true
,这样会在项目中禁用所有标准库。更多内容请点击
Compilation
。
为了手动的去加载一些全局声明文件,我们需要告诉TypeScript编译器声明文件的位置。可以通过
typeRoots
和
types
编译选项设置,关于这些内容,我也在
Compilation
里面讲解过。
接下来,动手实践。创建以下文件结构。
project/
types/
common/
main.d.ts
package.json
program.ts
tsconfig.json
在我们的项目中,我们需要一些公共类型,在每个ts文件中都可以使用这些公共类型,所以我打算在
project/types/common/main.d.ts
中存放这些公共类型声明,使用
typeRoots
告诉TypeScript声明文件的位置。TypeScript编译器会根据
Node模块定位策略
导入
typeRoots
指定的目录下的声明文件,如果没有提供
typeRoots
选项,默认会导入
node_modules/@types
文件夹中的声明文件。
node_modules/@types
是
typeRoots
的默认值,
node_modules/@types
的目的仅仅是为了提供公共包的类型声明,目录下的每个声明模块的根目录下都应该包含一个
index.d.ts
文件,用来作为typescirpt入口声明文件,如果没有
index.d.ts
文件,它应该有一个
package.json
文件,
package.json
文件需要指出入口声明文件的位置。
现在我需要把
project/types/common/main.d.ts
文件,作为入口声明文件,所以在
project/types/common/package.json
文件中需要添加以下内容。
{
"name": "common",
"version": "1.0.0",
"typings": "main.d.ts"
typings
用于指示入口声明文件的位置。
接下来,配置
typeRoots
,告诉TypeScript自定义全局声明的位置,修改
tsconfig.json
文件内容如下。
{
"files": [
"./program.ts"
"compilerOptions": {
"typeRoots": [
"./types"
在
main.d.ts
文件中添加声明信息。
interface Person {
firstName: string;
lastName: string;
age: number;
有了声明信息,可以在
program.ts
中使用该类型了。
正如图片中显示的那样,定义了
Person
类型的
ross
变量,因为赋值与类型不兼容,所以IDE报错,IDE还给出了声明文件的位置。修改program.ts文件如下,修复错误。
const ross: Person = {
firstName: "Ross",
lastName: "Geller",
age: 20
三.声明模块化
#
在上面的例子中,TypeScript仅仅知道
main.d.ts
声明文件,假如项目中包含几千个声明信息,将这些声明全部放到
main.d.ts
文件中,会使项目变得非常糟糕,不好维护。因此,需要将声明模块化。
在
命名空间
教程中,我介绍过可以使用
<reference />
指令,在编译阶段添加一些文件。当然我们也可以在声明文件中使用该指令。首先
project/types/common/
文件夹下添加
functions.d.ts
和
interfaces.d.ts
文件。
interfaces.d.ts
提供
common
包中所有的接口类型信息,
functions.d.ts
提供
common
包中所有的函数类型信息。并在
main.d.ts
文件中,使用
<reference />
指令引入它们。
-----------------------------------------------
interface Person {
firstName: string;
lastName: string;
age: number;
------------------------------------------------
type GetFullName = ( p: Person ) => string;
有一点值得注意的是,我们可以在
functions.d.ts
文件中,使用
interfaces.d.ts
文件中定义的
Person
接口,这是因为使用
<reference />
指定,指定的文件可以共享类型声明。
修改
program.ts
文件使用定义的类型。
const ross: Person = {
firstName: "Ross",
lastName: "Geller",
age: 20
const getPersonName: GetFullName = (p: Person) => {
return `${p.firstName} ${p.lastName}`;
console.log(getPersonName(ross));
四.第三方声明
#
目前,已经知道了如何创建自定义全局声明文件。某些情况下,我们需要安装第三方提供的npm声明文件包。
在
Compilation
文章中我提到过
DefinitelyTyped
社区,它们为我们提供了很多第三方npm声明文件包,例如
@types/lodash
和
@types/node
等等。你可以使用npm安装
@types
下的声明包。
npm install -D @types/node
这些包会被下载到
node_modules/@types
文件夹下,这也是为什么
typeRoots
的默认值为
node_modules/@types
。如果你想在默认值的基础上额外添加一个声明文件夹目录,需要给
typeRoots
指定两个值。
{
"files": [
"./program.ts"
"compilerOptions": {
"typeRoots": [
"./node_modules/@types",
"./types"
五.环境相关的声明
#
某些值,只存在于运行时。例如
window
对象,它只存在于浏览器环境中,同样的还有node中的
global
。
如果当你在TypeScript程序中,使用
window
或者
glbal
时,typescirpt编译器报错了
cannot find name 'window'
。这是正常的,因为没有定义
window
变量。所以,如果没有定义,我们如何使用
window
对象呢?
我们需要使用
declare
关键字告诉TypeScript编译器,
window
变量存在,不要再报错了。
declare var window: any;
这句代码,被称为
环境声明(ambient declaration)
,其中
window
被称为
环境值(ambient value)
。有了这个声明,TypeScript会认为这个
any
类型的
window
变量已经定义了,所以在任何地方使用
window
变量都不会报错了。
你也可以为环境值提供指定的类型,例如
declare var window: Person;
接下来,在
main.d.ts
文件中添加一个环境值
XiaoMing
。
declare var XiaoMing: Person;
然后在
program.ts
中使用它。
console.log(XiaoMing.age);
如果没有写错的话,TypeScript不会报错。
六.命名空间的声明
#
在前面的例子中,我们提供了
types/common
声明包,定义了全局
Person
接口类型,全局的接口类型有一个弊端,就是很容易被其他包里的声明文件类型覆盖(类型冲突)。
为了避免这个问题,最好的解决方式是将类型定义在命名空间里。在
namespace
文章中,介绍了namespace的用法,我们可以在命名空间里定义类型声明。
修改声明文件内容如下。
--------------------------------------------------------------------
declare namespace common {
interface Person {
firstName: string;
lastName: string;
age: number;
--------------------------------------------------------------------
declare namespace common {
type GetFullName = ( p: Person ) => string;
这里定义的命名空间类型与
namespace
文章中有点不一样,区别在于使用了declare关键字。因为
namespace
是一个值(object),在声明文件
.d.ts
文件中应该将
namespace
定义成环境值,这样我们可以在任何
ts
文件中,通过
common
命名空间使用
common.<type>
类型,例如
common.Person
。
再次提醒,在声明文件中只允许包含声明信息,因为命名空间是一个object,所以不能在声明文件中定义它,但是我们可以将命名空间定义成环境值。
并且,当命名空间被定义在声明文件中,
export
关键字就不需要了,命名空间内部的所有类型,都被隐式export。另外,你不能从命名空间中导出一个值,只允许导出类型。
七.类型扩展
#
当typescript预见多个同名接口类型,它会自动合并这些接口类型成为一个声明。
namespace也有类似的行为,当声明一个已经存在的命名空间时,typescript会自动扩展已经存在的命名空间。所以当接口和命名空间在全局环境中,可以重新声明它们来达到扩展它们的目的。
更新声明文件。
--------------------------------------------------------
interface Person {
firstName: string;
lastName: string;
age: number;
--------------------------------------------------------
declare namespace common {
type GetFullName = ( p: Person ) => string;
更新
program.ts
。
interface Person {
email: string;
interface Number {
isEven(): boolean;
const ross: Person = {
firstName: "Ross",
lastName: "Geller",
age: 29,
email: "[email protected]"
const isAgeEven = ross.age.isEven();
console.log("isAgeEven", isAgeEven);
此时typescript并不会报错。在
program.ts
中扩展了
Person
接口和
Number
(内置类型)接口。
但是,这并不适用于模块系统,值和类型只能在
script
文件中共享,
script
文件就是不包含
import
和
export
语句的文件。
可以在
program.ts
文件最后面添加一行代码
export {}
。此时typescript应该会给出以下错误信息。
Type '{ firstName: string; lastName: string; age: number; email: string; }' is not assignable to type 'Person'.
Object literal may only specify known properties, and 'firstName' does not exist in type 'Person'.ts(2322)
Property 'age' does not exist on type 'Person'.ts(2339)
因为,添加
export {}
代码,将
program.ts
文件转换成了模块(module),这种情况下,typescript不会扩展全局环境中的类型,而是在当前模块内重新声明该类型。
那么如何在模块中,扩展全局环境中的类型呢?TypeScript提供了
declare global
环境声明语句,使用它,可以在模块内给全局作用域添加声明。
更新
program.ts
,使用
declare global
语句。
declare global {
interface Person {
email: string;
declare global {
interface Number {
isEven(): boolean;
var version: string;
const ross: Person = {
firstName: 'Ross',
lastName: 'Geller',
age: 29,
email: '[email protected]'