外部声明

TypeScript 语言主要有两种类型的源文件:

  • 文件扩展名为 .ts.tsx 的文件。

  • 文件扩展名为 .d.ts 的文件。

.ts.tsx 文件中包含了应用的实现代码,它也是开发者日常编写的代码。.ts.tsx 文件中既可以包含类型声明又可以包含可执行代码。在编译 TypeScript 程序的过程中,.ts.tsx 文件会生成对应的 .js.jsx 文件。值得一提的是,.tsx 文件中包含了使用 JSX 语法编写的代码。JSX 采用了类似于 XML 的语法,JSX 因知名的 React 框架而流行,因为 React 框架推荐使用 JSX 来编写应用程序。

.d.ts 文件是类型声明文件,其中字母 d 表示 declaration,即声明的意思。.d.ts 文件只提供类型声明,不提供任何值,如字符串和函数实现等。因此,在编译 TypeScript 程序的过程中,.d.ts 文件不会生成对应的 .js 文件。

假设有如下目录结构的工程:

C:\app
|-- index.ts
|-- typings.d.ts
`-- tsconfig.json

在编译后,工程的目录结构如下:

C:\app
|-- index.js
|-- index.ts
|-- typings.d.ts
`-- tsconfig.json

我们能够看到,只有 index.ts 文件编译生成了对应的 index.js 文件,而 typings.d.ts 声明文件没有生成对应的 JavaScript 文件。该行为与两个文件中是否包含代码无关,就算 index.ts 是空文件也会生成对应的 index.js 文件。

我们可以将 .d.ts 文件称作外部声明文件或简称为声明文件。声明文件中的内容是外部声明。外部声明用于为已有代码提供静态类型信息以供 TypeScript 编译器使用。例如,知名代码库 jQuery 的外部声明文件提供了 jQuery API 的类型信息。TypeScript 编译器能够利用该类型信息进行代码静态类型检查以及代码自动补全等操作。

外部声明是 TypeScript 语言规范中使用的术语。我们不必纠结于如何划分外部和内部,它是一个相对概念。外部声明也可以出现在 .ts 文件中,我们只需明确外部声明是类型声明而不是具体实现,外部声明在编译后不会输出任何可执行代码即可。外部声明包含以下两类:

  • 外部类型声明。

  • 外部模块声明。

外部类型声明

外部类型声明通过 declare 关键字来定义,包含外部变量声明、外部函数声明、外部类声明、外部枚举声明和外部命名空间声明。

外部变量声明

外部变量声明定义了一个变量类型,它的语法如下所示:

declare var a: boolean;

declare let b: boolean;

declare const c: boolean;

此例中定义了三个外部变量声明。外部变量声明不允许定义初始值,因为它只表示一种类型,而不能够表示一个值。如果外部变量声明中没有使用类型注解,那么变量的类型为 any 类型。

例如,有如下目录结构的工程:

C:\app
|-- index.ts
|-- typings.d.ts
`-- tsconfig.json

typings.d.ts 文件的内容如下:

declare var Infinity: number;

index.ts 文件的内容如下:

const x = 10 ** 10000;

if (x === Infinity) {
    console.log('Infinity');
}

此例中,编译器能够从 typings.d.ts 文件中获取 Infinity 变量的类型。实际上,InfinityJavaScript 语言内置的一个全局属性,它表示一个无穷大的数值。

外部函数声明

外部函数声明定义了一个函数类型,它的语法如下所示:

declare function f(a: string, b: boolean): void;

外部函数声明使用 declare function 关键字来定义。外部函数声明中不允许带有函数实现,只能定义函数类型。

例如,有如下目录结构的工程:

C:\app
|-- index.ts
|-- typings.d.ts
`-- tsconfig.json

typings.d.ts 文件的内容如下:

declare function alert(message?: any): void;

index.ts 文件的内容如下:

alert('Hello, World!');

此例中,编译器能够从 typings.d.ts 文件中获取 alert 函数的类型。实际上,alert 函数是 JavaScript 语言内置的一个全局函数,它能够在浏览器中显示一个弹窗消息。

外部类声明

外部类声明定义了一个类类型,它的语法如下所示:

declare class C {
    // 静态成员声明
    public static s0(): string;
    private static s1: string;

    // 属性声明
    public a: number;
    private b: number;

    // 构造函数声明
    constructor(arg: number);

    // 方法声明
    m(x: number, y: number): number;

    // 存取器声明
    get c(): number;
    set c(value: number);

    // 索引签名声明
    [index: string]: any;
}

外部类声明使用 declare class 关键字来定义。外部类声明中的成员不允许带有具体实现,只允许定义类型。例如,类的方法和构造函数不允许带有具体实现,类的属性声明不允许定义初始值等。

例如,有如下目录结构的工程:

C:\app
|-- index.ts
|-- typings.d.ts
`-- tsconfig.json

typings.d.ts 文件的内容如下:

declare class Circle {
    readonly radius: number;
    constructor(radius: number);
    area(): number;
}

index.ts 文件的内容如下:

const circle = new Circle(10);
const area: number = circle.area();

此例中,编译器能够从 typings.d.ts 文件中获取 Circle 类的类型。注意,若想要实际运行此例中的代码,我们还需要给出 Circle 类的具体定义。typings.d.ts 中只声明了 Circle 类的类型,而没有提供具体定义。

外部枚举声明

外部枚举声明定义了一个枚举类型。外部枚举声明与常规的枚举声明的语法是相同的,具体如下所示:

declare enum Foo {
    A,
    B,
}

declare enum Bar {
    A = 0,
    B = 1,
}

declare const enum Baz {
    A,
    B,
}

declare const enum Qux {
    A = 0,
    B = 1,
}

外部枚举声明与常规枚举声明主要有以下两点不同:

  • 在外部枚举声明中,枚举成员的值必须为常量枚举表达式,例如数字字面量、字符串字面量或简单算术运算等。

  • 在使用了 declare enum 的外部枚举中,若枚举成员省略了初始值,则会被视为计算枚举成员,因此不会被赋予一个自增长的初始值,如 0、1 和 2 等。

例如,有如下目录结构的工程:

C:\app
|-- index.ts
|-- typings.d.ts
`-- tsconfig.json

typings.d.ts 文件的内容如下:

declare enum Direction {
    Up,
    Down,
    Left,
    Right,
}

index.ts 文件的内容如下:

let direction: Direction = Direction.Up;

此例中,编译器能够从 typings.d.ts 文件中获取 Direction 枚举的类型。注意,若想要实际运行此例中的代码,我们还需要给出 Direction 枚举的具体定义。typings.d.ts 中只声明了 Direction 枚举的类型,而没有提供具体定义。

外部命名空间声明

外部命名空间声明定义了一个命名空间类型,它的语法如下所示:

declare namespace Foo {
    // 外部变量声明
    var a: boolean;
    let b: boolean;
    const c: boolean;

    // 外部函数声明
    function foo(bar: string, baz: boolean): void;

    // 外部类声明
    class C {
        x: number;
        constructor(x: number);
        y(): void;
    }

    // 接口声明
    interface I {
        x: number;
        y: number;
    }

    // 外部枚举声明
    enum E {
        A,
        B,
    }

    // 嵌套的外部命名空间声明
    namespace Inner {
        var a: boolean;
    }
}

外部命名空间的成员默认为导出成员,不需要使用 export 关键字来明确地导出它们,但使用了 export 关键字也没有错误。示例如下:

declare namespace Foo {
    export var a: boolean;
}

例如,有如下目录结构的工程:

C:\app
|-- index.ts
|-- typings.d.ts
`-- tsconfig.json

typings.d.ts 文件的内容如下:

declare namespace Drawing {
    interface Point {
        x: number;
        y: number;
    }
}

index.ts 文件的内容如下:

const point: Drawing.Point = { x: 0, y: 0 };

此例中,编译器能够从 typings.d.ts 文件中获取 Drawing 命名空间的类型。

外部模块声明

外部模块声明定义了一个模块类型。外部模块声明只能在文件的顶层定义,并且存在于全局命名空间当中。外部模块声明的语法如下所示:

declare module 'io' {
    export function readFile(filename: string): string;
}

在该语法中,declare module 是关键字,它后面跟着一个表示模块名的字符串,模块名中不能使用路径。

例如,有如下目录结构的工程:

C:\app
|-- index.ts
|-- typings.d.ts
`-- tsconfig.json

typings.d.ts 文件的内容如下:

declare module 'io' {
    export function readFile(filename: string): string;
}

index.ts 文件的内容如下:

import { readFile } from 'io';

const content: string = readFile('hello.ts');