搞懂TypeScript的类型声明

.d.ts 类型声明/定义文件是 TypeScript 重要的一部分, 希望本文可以带你搞懂下面几个问题:

  • 类型声明是怎么被TypeScirpt识别到的?
  • 怎么使用第三方包的类型声明文件?
  • TypeScript代码怎么编译生成类型声明文件?
  • 怎么为第三方包编写类型声明文件,第三方包类型错误怎么进行修正

类型声明文件

TypeScript 中有两种主要的文件类型 .ts.d.ts,以 .d.ts 为后缀的文件,我们称为 TypeScript 的类型声明文件,主要是用来描述模块内所有导出接口的类型信息(声明文件只是对类型进行定义用来做类型检查,不是代码实现,不能进行赋值)。

类型声明是怎么被引用到的

1.TypeScript 内置类型声明

当你安装 TypeScript 时,会自带很多类型声明:lib。其中 es5.full.d.ts 声明文件包含 JavaScript 运行时以及 DOM 中存在各种常见的环境声明。

image.png

以 DOM 类型为例:

const aEle = document.querySelector('a') // HTMLAnchorElement | null
const canvasEle = document.querySelector('#my_canvas') as HTMLCanvasElement

我们查找a标签,TypeScript 可以自动推断出类型为 HTMLAnchorElement | null

如果通过元素ID查找,TypeScript无法推断出具体的元素类型,我们可以使用类型断言来明确指定元素类型为 HTMLCanvasElement(关于断言内容可以阅读:搞懂TypeScript中的类型断言和使用场景)。当变量有明确的类型之后,我们也可以等到更好的类型校验和语法提示。

上面说到的 HTMLAnchorElementHTMLCanvasElement 就是 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-extractortemp 中的多个 .d.ts 进行处理和合并。

注意:package.json 使用 typestypings 来指定类型声明文件,而非 typetype 用来指定文件模块方案,通常为 modulecommonjs(默认)。

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标识:

image.png

DefinitelyTyped中维护类型定义,会展示有DT标识:

image.png

TypeScript编写的项目,怎么生成类型定义文件

使用TypeScript 开发的项目中可以通过 tsc 来编译生成类型声明文件,在tsconfig.json中配置一下编译选项:

// tsconfig.json
{
    "compilerOptions": {
        "declaration": true, // 用于指定是否在编译完成后生成相应的*.d.ts文件
        "emitDeclarationOnly": true // 只生成声明文件, 不会生成js文件
    }
}

也可以通过命令行指定参数:

tsc --declaration --emitDeclarationOnly

和上文介绍的 vue 一样,在 package.json 中通过 "types""typings" 字段来指定类型声明文件即可。

指定编译范围

指定待编译文件有两种方式:

  • 使用 files 属性
  • 使用 includeexclude 属性

如果 filesinclude 都未设置,那么除了 exclude 排除的文件,编译器会默认包含路径下的所有 TS 文件

如果同时设置 filesinclude ,那么编译器会把两者指定的文件都引入。

如果未设置 exclude ,那其默认值为 node_modulesbower_componentsjspm_packages 和编译选项 outDir 指定的路径。

exclude 只对 include 有效,对 files 无效。即 files 指定的文件如果同时被 exclude 排除,那么该文件仍然会被编译器引入。

前面提到,任何被 filesinclude 引入的文件的依赖会被自动引入。

为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 文件的 includefiles 中。

{  
"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"
  ]
}

这种继承有两种特点:

  • 继承者中的同名配置会覆盖被继承者
  • 所有相对路径都被解析为其所在文件的路径

参考文档

理解 Typescript 配置文件

Definitely Typed

欢迎关注公众号“混沌前端”

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容