• 搞懂TypeScript的类型声明


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

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

    类型声明文件

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

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

    1.TypeScript 内置类型声明

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

    img

    以 DOM 类型为例:

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

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

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

    上面说到的 HTMLAnchorElementHTMLCanvasElement 就是 TypeScript 内置的 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",
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    vue 是使用 TypeScript 编写的,在 npm scripts build:types 中通过 tsc 命令来生成类型定义文件到临时文件夹 temp

    tsc --declaration --emitDeclarationOnly --outDir temp
    
    • 1

    并通过 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"]
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意: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文件
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    tsc --declaration --emitDeclarationOnly
    
    • 1

    和上文介绍的 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';
    
    • 1
    • 2

    上面的代码中我们声明了my-library-a,但是没有任何类型定义,就会的到隐私的any类型。使用第三方包时,TypeScript总提示类型错误或者缺少类型定义,又不想写类型,就可以使用这种方式。

    如果我们只使用到了第三方库的某些方法,或者第三方库的一些类型出现错误,可以使用 declare 进行类型补充

    // global.d.ts
    declare module 'my-library-b' {  
        export function myFunction(): void;  
        export const myVariable: string;  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 声明全局变量、函数、对象
    // global.d.ts
    declare const A: string;  
    declare function B(): void;  
    declare interface User {  
        name: string;  
        age: number;  
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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 文件夹下  
    ]  
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在上述示例中,我们将 typings/*.d.ts 添加到 include 数组中,以确保 TypeScript 编译器会包含 typings 文件夹下的所有 .d.ts 文件。

    tsconfig.json配置复用

    我们可以使用 extends 来实现配置复用,即一个配置文件可以继承另一个文件的配置属性。

    比如,建立一个基础的配置文件 configs/base.json

    {
      "compilerOptions": {
        "noImplicitAny": true,
        "strictNullChecks": true
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后,tsconfig.json 就可以引用这个文件的配置了:

    {
      "extends": "./configs/base",
      "files": [
        "main.ts",
        "supplemental.ts"
      ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这种继承有两种特点:

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

    理解 Typescript 配置文件

    Definitely Typed

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

  • 相关阅读:
    给你的 SpringBoot 工程部署的 jar 包瘦瘦身吧!
    Android 开发学习(三)
    OneFlow-ONNX v0.6.0正式发布
    [RK3568 Android11]libusb流程简介
    基于单片机的多关节机械臂抓取系统
    一文带你了解如何编写自动化测试用例
    码农死磕这份Java高级开发文档,成功‘挤‘进一线大厂,这也太强了吧
    Git基本概念与使用
    CS110L(Rust)
    ModStart - 模块化开发框架的引领者
  • 原文地址:https://blog.csdn.net/w20101310/article/details/133950251