相关文章推荐
没人理的豆芽  ·  asp.net mvc - Using ...·  1 年前    · 
宽容的楼梯  ·  Migration guide for ...·  1 年前    · 

在使用第三方库的时候,想使用 typescript 类型检查、自动补全等等功能,需要一个描述 javascript 库和模块信息的声明文件。通常来说,都是将声明语句放到一个单独文件中 *.d.ts

对于第三方库,目前也是 DefinitelyTyped 推荐的两种方式:

  • 如果是开发者,且使用的也是 typescript ,那么推荐在包里捆绑自动生成的声明文件。补充:在 tsconfig.json 里,可以设置以下属性去自动生成声明文件:
  • declaration :设置可以自动生成 *.d.ts 声明文件
  • declarationDir :设置生成的 *.d.ts 声明文件的目录
  • declarationMap :设置生成 *.d.ts.map 文件(sourceMap)
  • emitDeclarationOnly :不生成 js 文件,仅仅生成 *.d.ts *.d.ts.map
  • 如果不是开发者或者不是用 typescript ,那么可以选择发起一个 PR DefinitelyTyped 。如果合并到了 master 上,会自动发布到 npm 上。即: @types/xxx
  • 声明文件和普通文件

    *.d.ts *.ts 的区别在于:

  • *.d.ts 对于 typescript 而言,是类型声明文件,且在 *.d.ts 文件中的顶级声明必须以 declare export 修饰符开头。同时在项目编译过后, *.d.ts 文件是不会生成任何代码的。补充:默认使用 tsc —init 会开启 skipLibCheck 跳过声明文件检查,可以关闭它。
  • *.ts 则没有那么多限制,任何在 *.d.ts 中的内容,均可以在 *.ts 中使用。
  • 自动引入@types

    同时根据 文档 ,在 typescript 2.0 以后,默认所有可见的 @types 包,会在编译过程中包含进来,例如: ./node_modules/@types/ ../node_modules/@types/ ../../node_modules/@types/ 等等。

    但是,如果指定了 typeRoots 或者 types ,那么只有 typeRoots 目录下的包才会被引入,或者被 types 指定的包。

    例如:设置 "types": [] 会禁用自动引入 @types 包的功能。

    声明文件实现

    以下语法并不仅仅只能在声明文件中,只是说,相当于普通文件书写,更频繁出现在声明文件中。

    declare

    在声明文件中,最常看见的语法之一。用来全局声明变量、常量、类、全局对象等等,前提是该文件不是模块声明文件(后面会讲)。

    declare const Jye1: string;
    declare let Jye2: string;
    declare class Jye3 {}
    declare namespace Jye4 {}
    // ...
    

    同时在声明函数的时候,也是支持函数重载的。

    declare function name(params: string): void;
    declare function name(params: number): number;
    

    在使用declare声明类型的时候,并不能去定义具体的实现过程。

    比较特别的,像是通过declare global,可以拓展全局变量的类型和方法。

    // ./types/test.d.ts
    declare global {
      interface String {
        helloword(): string;
    export {};
    
    // ./src/test.ts
    const test = "jye";
    test.helloword();
    

    如果不加export {},会报「全局范围的扩大仅可直接嵌套在外部模块中或环境模块声明中」错误。增加export{}其实也就是为了让这个声明文件变成模块声明文件,而不是一个全局声明文件。

    前言:在typescript 1.5里,内部模块被称做「命名空间」,外部模块称为「模块」。同时module X {相当于现在推荐的写法namespace X {文档

    namespace一开始的提出,主要是为了模块化(防止命名冲突等等)。但是ES6普及之后,namespace已经不再推荐使用了,更推荐使用ES6模块化。但是,在声明文件中namespace比较常见的。

    命名空间表示一个全局变量是一个对象,可以定义很多属性类型。同时命名空间里可能会用到一些接口类型(interfacetype),这时候一般有两种写法:

  • 写在namespace外层,会作为全局类型被引入,从而可能污染全局类型空间。
  • 写在namespace里层,在想使用该类型的时候,可以通过namespace.interface进行使用。(推荐)
  • // ./types/test.d.ts
    declare namespace Jye {
      interface Info {
        name: string;
        age: number;
      function getAge(): number;
    
    // ./src/test.ts
    let settings: Jye.Info = {
      name: "jye",
      age: 8,
    Jye.getAge();
    

    同时,命名空间支持嵌套使用,即:namespace嵌套namepsace。或者简化的写法,可以写成namepsace.namespace进行声明。

    同时命名空间也支持声明合并。

    // ./types/test.d.ts
    declare namespace Jye.Eee {
      interface Api {
        getInfo(): Info;
    

    三斜线指令

    三斜线指令,也是最初用来表示模块之间依赖关系。目前也是很少会去使用,不过声明文件中,还是有很多会去使用。

    在三斜线指令的语法中,目前可能会去比较常用的两种语法:

  • /// <reference path="./lib/index.d.ts" />:表示对一个文件的依赖。
  • /// <reference types="jye" />:表示对一个库的依赖。
  • 说白了,三斜线的path & types,和es6import语义相似,同时三斜线指令必须放在文件的最顶端。例如,当我们的声明文件过于庞大,一般都会采用三斜线指令,将我们的声明文件拆分成若干个,然后由一个入口文件引入。

    npm包捆绑的声明文件语法

    在配置tsconfig.json设置declarationture去自动生成声明文件或者是手动去写声明文件,比较常见的语法,像是:

  • export:导出变量
  • export default: 默认导出
  • export namespace:导出对象
  • export =:commonJS导出
  • npm包的声明文件相对于之前的全局声明文件而言,可以理解为是局部声明文件。只有当通过import引入npm包后,才能使用对应的声明类型。而前三个语法,其实和es6类似,用法语义一目了然。

    比较特殊的是,export =对应的像是import xxx = require。其实使用都是类似的,只是为了兼容AMDcommonJS才有的语法。文档

    其实也就是说,对于一个npm包的声明文件,只有通过export导出的类型,才能被使用。

    全局声明和局部声明

    其实写到这里,前面有两点没有说清楚,什么是全局声明,什么是局部声明。

    我的理解是,如果这个声明文件被typescript引入了,那么这个文件不包含import export,那么这个文件中包含的declare & interface & type就会变成全局声明。反之,若是这个文件包含了import export,那么这个文件包含的declare & interface & type则会是局部声明,不会影响到全局声明。

    @types/react为例:配置tsconfig.json关闭自动引入@types文件,且在@types/react中增加declare

    // @types/react/index.d.ts
    export = React;
    export as namespace React;
    declare namespace Jye {
      interface Info {
        name: string;
        age: number;
      function getAge(): number;
    

    同时在a文件import React from 'react,在b文件使用相关类型

    // src/b.ts
    React.Children;  // ok
    let settings: Jye.Info = {  // 找不到命名空间“Jye”。ts(2503)
      name: 'jye',
      age: 8,
    Jye.getAge(); // 找不到命名空间“Jye”。ts(2503)
    

    可以看到,在项目中引入了react后,那么该文件导出的类型则被引入到全局中。但是除却export出来的类型,其他declare的类型,则无法被使用。

    同理,可以在项目中,定义*d.ts,通过设置export {}将其从一个全局声明文件变成一个模块声明文件。那么对应declare内容则会无法使用,只能通过引入文件后,使用其export出来的类型。

    那么总结:如果没有export import,那么这个文件被引入后,则会是一个全局声明,(也就是说这个文件是全局声明文件)。否则,这个文件被引入后,仅仅其export的内容,被引入到全局里,其他内容则作为局部声明(这个文件是模块声明文件)。

    在项目中,设置package.jsontypes或者typings指向声明文件。在设置types或者typings后,会去找指向的声明文件。如果没有定义,则会去找根目录下的index.d.ts,再没有则去找入口文件,是否存在对应文件名的声明文件。

    具体typescript如何解析查找模块类型,可以看这篇文章传送门

    可以通过以下方法去让typescript引入类型:

  • tsconfig.json配置types指定我们的包名。
  • 在项目中,通过import手动导入我们的包。
  • 在项目中,通过三斜线指令引用。
  • www.typescriptlang.org/

    jkchao.github.io/typescript-…

    ts.xcatliu.com/

    分类:
    前端
    标签: