搞懂TypeScript的类型声明
.d.ts
类型声明/定义文件是 TypeScript 重要的一部分, 希望本文可以带你搞懂下面几个问题:
- 类型声明是怎么被TypeScirpt识别到的?
- 怎么使用第三方包的类型声明文件?
- TypeScript代码怎么编译生成类型声明文件?
- 怎么为第三方包编写类型声明文件,第三方包类型错误怎么进行修正
类型声明文件
TypeScript 中有两种主要的文件类型 .ts
和 .d.ts
,以 .d.ts
为后缀的文件,我们称为 TypeScript 的类型声明文件,主要是用来描述模块内所有导出接口的类型信息(声明文件只是对类型进行定义用来做类型检查,不是代码实现,不能进行赋值)。
类型声明是怎么被引用到的
1.TypeScript 内置类型声明
当你安装 TypeScript 时,会自带很多类型声明:lib。其中 es5.full.d.ts 声明文件包含 JavaScript 运行时以及 DOM 中存在各种常见的环境声明。
以 DOM 类型为例:
const aEle = document.querySelector('a') // HTMLAnchorElement | null
const canvasEle = document.querySelector('#my_canvas') as HTMLCanvasElement
我们查找a
标签,TypeScript 可以自动推断出类型为 HTMLAnchorElement | null
。
如果通过元素ID查找,TypeScript无法推断出具体的元素类型,我们可以使用类型断言来明确指定元素类型为 HTMLCanvasElement
(关于断言内容可以阅读:搞懂TypeScript中的类型断言和使用场景)。当变量有明确的类型之后,我们也可以等到更好的类型校验和语法提示。
上面说到的 HTMLAnchorElement
和 HTMLCanvasElement
就是 TypeScript 内置的 DOM 类型声明,也就是 <reference lib="dom" />
的引用内容,具体内容可以参考:dom.generated.d.ts。
2.第三方包自带的类型声明
一些第三方包会自带类型声明文件,通过 package.json
文件中的 "types"
或 "typings"
字段来指定声明文件位置,以 vue 为例:
{
"name": "vue",
"typings": "types/index.d.ts",
"exports": {
".": {
"import": {
"node": "./dist/vue.runtime.mjs",
"default": "./dist/vue.runtime.esm.js"
},
"require": "./dist/vue.runtime.common.js",
"types": "./types/index.d.ts"
},
"./dist/*": "./dist/*",
"./types/*": "./types/*",
"./package.json": "./package.json"
},
"scripts": {
"build:types": "rimraf temp && tsc --declaration --emitDeclarationOnly --outDir temp && api-extractor run && api-extractor run -c packages/compiler-sfc/api-extractor.json",
"test:types": "npm run build:types && tsc -p ./types/tsconfig.json",
"format": "prettier --write --parser typescript "(src|test|packages|types)/**/*.ts"",
"ts-check": "tsc -p tsconfig.json --noEmit",
"ts-check:test": "tsc -p test/tsconfig.json --noEmit",
}
}
vue 是使用 TypeScript 编写的,在 npm scripts build:types
中通过 tsc 命令来生成类型定义文件到临时文件夹 temp
:
tsc --declaration --emitDeclarationOnly --outDir temp
并通过 api-extractor 将 temp
中的多个 .d.ts
进行处理和合并。
注意:
package.json
使用types
和typings
来指定类型声明文件,而非type
,type
用来指定文件模块方案,通常为module
或commonjs
(默认)。
3.社区维护的声明文件
一些项目中使用的第三方包可能不是使用 TypeScript 编写的,没办法通过代码直接编译生成 .d.ts
声明文件,也没有在包内维护类型声明文件,我们就可以在 TypeScript 社区查找方案。
Definitely Typed是社区维护的声明类库,一些不是使用TypeScript编写的项目,我们可以找到想要的类型声明,极大的简化了JS项目迁移到TS的难度。安装的 @types/xxx
包,都是维护在 Definitely Typed 中。
比如@types/react
就可以在 DefinitelyTyped 中找到。
默认情况下项目中安装的@types/react
、@types/react-dom
都会被编译器自动引入,TypeScript 会从当前目录向上查找所有 node_modules/@types
中的包,也就是包含 ./node_modules/@types
、../node_modules/@types
等目录。
但是如果在 tsconfig.json 中通过 typeRoots
配置了指定目录,就会自动引入该目录下的类型声明模块。
{
"compilerOptions": {
"typeRoots": ["./typings", "node_modules/@types"]
}
}
注意:typeRoots 默认值为 “node_modules/@types” ,如果指定了其他目录就不会从默认目录中进行查找了,如果还想使用安装的 ”@types/xxx“ ,需要将默认值也添加进来。
怎么确认使用的第三方是否包含类型定义文件?
在 NPM 上会有对应的标签,使用TypeScript编写,会展示有TS
标识:
DefinitelyTyped中维护类型定义,会展示有DT
标识:
TypeScript编写的项目,怎么生成类型定义文件
使用TypeScript 开发的项目中可以通过 tsc
来编译生成类型声明文件,在tsconfig.json中配置一下编译选项:
// tsconfig.json
{
"compilerOptions": {
"declaration": true, // 用于指定是否在编译完成后生成相应的*.d.ts文件
"emitDeclarationOnly": true // 只生成声明文件, 不会生成js文件
}
}
也可以通过命令行指定参数:
tsc --declaration --emitDeclarationOnly
和上文介绍的 vue 一样,在 package.json
中通过 "types"
或 "typings"
字段来指定类型声明文件即可。
指定编译范围
指定待编译文件有两种方式:
- 使用
files
属性 - 使用
include
和exclude
属性
如果 files
和 include
都未设置,那么除了 exclude
排除的文件,编译器会默认包含路径下的所有 TS 文件。
如果同时设置 files
和 include
,那么编译器会把两者指定的文件都引入。
如果未设置 exclude
,那其默认值为 node_modules
、bower_components
、jspm_packages
和编译选项 outDir
指定的路径。
exclude
只对 include
有效,对 files
无效。即 files
指定的文件如果同时被 exclude
排除,那么该文件仍然会被编译器引入。
前面提到,任何被 files
或 include
引入的文件的依赖会被自动引入。
为JS或者第三方代码编写.d.t
在使用TypeScript时,你可能遇到以下问题:
- 自己使用JS编写的库,不想使用TypeScript重写,为其补充.d.ts
- 项目中使用的第三方库没有类型声明文件
- 第三方库缺少某些类型,或者错误定义了某些类型,需要对其进行补充和修正
1. 导入和导出类型定义
如果你想把某个模块相关的类型都定义在一个文件中,可以创建一个.ts
文件,并使用 export
导出,在其他文件中可以直接通过 import
引用。
2. 第三方包类型补充
某些库没有提供类型声明,或者声明类型和实际API不匹配,我们也可以在declare
中对其进行补充。
declare可以用来声明全局变量、函数、类或者增强模块的类型:
-
声明模块:当使用第三方库时,你可以使用
declare
关键字来声明模块的类型,这样可以避免 TypeScript 报错,也可以有更好的类型检查和自动补全。
// global.d.ts
declare module 'my-library-a';
上面的代码中我们声明了my-library-a
,但是没有任何类型定义,就会的到隐私的any
类型。使用第三方包时,TypeScript总提示类型错误或者缺少类型定义,又不想写类型,就可以使用这种方式。
如果我们只使用到了第三方库的某些方法,或者第三方库的一些类型出现错误,可以使用 declare
进行类型补充
// global.d.ts
declare module 'my-library-b' {
export function myFunction(): void;
export const myVariable: string;
}
- 声明全局变量、函数、对象:
// global.d.ts
declare const A: string;
declare function B(): void;
declare interface User {
name: string;
age: number;
}
3. 生成.d.ts工具
微软还提供了一个为第三方库编写声明文件工具: dts-gen,使用dts-gen,可以 从任何 JavaScript 对象生成 TypeScript 定义文件 (.d.ts)。
如果你想为自己写的JS代码或者项目中引入的 NPM 模块编写类型声明,可以使用dts-gen,它会进行类型推断自动生成.d.ts文件。
最后,要让编写的类型声明文件(.d.ts
)在项目中生效并使用,你需要将其添加到 tsconfig.json
文件的 include
或 files
中。
{
"compilerOptions": {
// 其他配置项...
},
"include": [
"src/**/*.ts",
"typings/*.d.ts" // 假设你的类型声明文件在 typings 文件夹下
]
}
在上述示例中,我们将 typings/*.d.ts
添加到 include
数组中,以确保 TypeScript 编译器会包含 typings
文件夹下的所有 .d.ts
文件。
tsconfig.json配置复用
我们可以使用 extends
来实现配置复用,即一个配置文件可以继承另一个文件的配置属性。
比如,建立一个基础的配置文件 configs/base.json
:
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true
}
}
然后,tsconfig.json
就可以引用这个文件的配置了:
{
"extends": "./configs/base",
"files": [
"main.ts",
"supplemental.ts"
]
}
这种继承有两种特点:
- 继承者中的同名配置会覆盖被继承者
- 所有相对路径都被解析为其所在文件的路径
参考文档
欢迎关注公众号“混沌前端”