A Type Declaration or Type Definition file is a TypeScript file with a .d.ts file extension. What is the difference between them and a normal .ts file? What are their characteristics? Next, let’s take a deeper look.

I. The .d.ts files

Type declaration files have the suffix .d.ts and contain only type-related code, not logical code. Their purpose is to provide type information to the developer, so they are only useful during the development phase.

Type declarations, i.e. declarations of types, such as interface, function and class, are shown below as an interface declaration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Person接口类型声明
interface Person {
    name: string;
    age: number;
}

// 使用Person接口
const xm: Person = {
    name: "XiaoMing",
    age: 8
};

II. Global declarations

global declaration works in any TypeScript project or TypeScript code fragment. For example, when you write const p = new Promise();, TypeScript compiler will not compile your code, because const p = new Promise(); syntax error, if you are using vscode IDE, then vscode prompt you as in the following figure (personal code, used to use white) background, vscode’s default background color is black, although the theme color is different, but the prompt is the same).

global declaration

vscode detects your syntax errors based on global declarations. Every TypeScript program can use global declarations without the need to display them imported with the import statement.

From the IDE prompt, you can see that the Promise type is defined in the lib.es2015.promise.d.ts file, provided by TypeScript, which provides many of these declaration files, called Standard Library (these declaration files are installed with TypeScript).

Using the lib compiler option, you can control which standard libraries are introduced into the current project. You can also set noLib to true, which will disable all standard libraries in the project. More on this can be found at Compilation.

In order to manually go through and load some global declaration files, we need to tell the TypeScript compiler where the declaration files are located. This can be set via the typeRoots and types compiler options.

Next, get hands-on. Create the following file structure.

1
2
3
4
5
6
7
project/
    types/
        common/
            main.d.ts
            package.json
    program.ts
    tsconfig.json

In our project, we need some public types that are available in each ts file, so I’m going to store these public type declarations in project/types/common/main.d.ts, using typeRoots to tell TypeScript the location of the declaration file. The TypeScript compiler will import the declaration files in the directory specified by typeRoots according to the Node module location policy, and if the typeRoots option, the declaration files in the node_modules/@types folder will be imported by default.

node_modules/@types is the default for typeRoots. The purpose of node_modules/@types is only to provide type declarations for public packages, and each declaration module in the directory should contain an index.d.ts file in the root directory to be used as the typescirpt If there is no index.d.ts file, it should have a package.json file, and the package.json file needs to point to the location of the entry declaration file.

Now I need to use the project/types/common/main.d.ts file as the entry declaration file, so I need to add the following to the project/types/common/package.json file.

1
2
3
4
5
{
    "name": "common",
    "version": "1.0.0",
    "typings": "main.d.ts"
}

typings is used to indicate the location of the entry declaration file.

Next, configure typeRoots to tell TypeScript to customize the location of the global declaration by modifying the tsconfig.json file as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
    "files": [
        "./program.ts"
    ],
    "compilerOptions": {
        "typeRoots": [
            "./types"
        ]
    }
}

Add the declaration information to the main.d.ts file.

1
2
3
4
5
interface Person {
    firstName: string;
    lastName: string;
    age: number;
}

With the declaration information, you can use the type in program.ts.

program.ts

As shown in the image, the ross variable of type Person is defined and the IDE reports an error because the assignment is not compatible with the type, and the IDE also gives the location of the declaration file. Modify the program.ts file as follows to fix the error.

1
2
3
4
5
const ross: Person = {
    firstName: "Ross",
    lastName: "Geller",
    age: 20
};

III. Declarations Modularity

In the above example, TypeScript only knows the main.d.ts declaration file. If the project contains thousands of declarations, putting them all in the main.d.ts file will make the project very bad and unmaintainable. Therefore, declarations need to be modularized.

First add the functions.d.ts and interfaces.d.ts files to the project/types/common/ folder.

interfaces.d.ts provides information about all the interface types in the common package, and functions.d.ts provides information about all the function types in the common package. And introduce them in the main.d.ts file using the <reference /> directive.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// types/common/main.d.ts
/// <reference path="./interfaces.d.ts" />
/// <reference path="./functions.d.ts" />

-----------------------------------------------
// types/common/interfaces.d.ts
interface Person {
    firstName: string;
    lastName: string;
    age: number;
}

------------------------------------------------
// types/common/functions.d.ts
type GetFullName = ( p: Person ) => string;

One thing worth noting is that we can use the Person interface defined in the interfaces.d.ts file in the functions.d.ts file, because with the <reference /> designation, the specified files can share type declarations.

Modify the program.ts file to use the defined types.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const ross: Person = {
    firstName: "Ross",
    lastName: "Geller",
    age: 20
};

const getPersonName: GetFullName = (p: Person) => {
    return `${p.firstName} ${p.lastName}`;
};

console.log(getPersonName(ross));

IV. Third-party declarations

So far, it is known how to create custom global declaration files. In some cases, we need to install the npm declaration file package provided by a third party.

The DefinitelyTyped community, they provide us with many third-party npm declaration file packages, such as @types/lodash and @types/node and so on. You can use npm to install the declaration packages under @types.

1
npm install -D @types/node

These packages are downloaded to the node_modules/@types folder, which is why the default value for typeRoots is node_modules/@types. If you want to add an additional declaration folder directory to the default value, you need to specify two values for typeRoots.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
    "files": [
        "./program.ts"
    ],
    "compilerOptions": {
        "typeRoots": [
            "./node_modules/@types",
            "./types"
        ]
    }
}

Certain values that exist only at runtime. For example, the window object, which exists only in the browser environment, and similarly the global in node globals.html#globals_global).

If you use window or glbal in a TypeScript program, the typescirpt compiler reports an error cannot find name 'window'. This is normal, because the window variable is not defined. So, how can we use the window object if it is not defined?

We need to use the declare keyword to tell the TypeScript compiler that the window variable exists and to stop reporting errors.

1
declare var window: any;

This code is called ambient declaration, where window is called ambient value. With this declaration, TypeScript assumes that the window variable of type any is already defined, so no errors will be reported anywhere the window variable is used.

You can also provide a specified type for the environment value, for example declare var window: Person;

Next, add an environment value XiaoMing to the main.d.ts file.

1
2
3
4
/// <reference path="./interfaces.d.ts" />
/// <reference path="./functions.d.ts" />

declare var XiaoMing: Person;

Then use it in program.ts.

1
console.log(XiaoMing.age);

TypeScript does not report an error if it is not written incorrectly.

VI. Namespace declarations

In the previous example, we provided the types/common declaration package, which defines the global Person interface type. The global interface type has the disadvantage that it is easily overwritten by the types of declaration files in other packages (type conflict).

To avoid this problem, the best solution is to define the types in namespaces.

Modify the contents of the declaration file as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// types/common/main.d.ts
/// <reference path="./interfaces.d.ts" />
/// <reference path="./functions.d.ts" />

--------------------------------------------------------------------
// types/common/interfaces.d.ts
declare namespace common {
    interface Person {
        firstName: string;
        lastName: string;
        age: number;
    }
}

--------------------------------------------------------------------
// types/common/functions.d.ts
declare namespace common {
    type GetFullName = ( p: Person ) => string;
}

Again, only declaration information is allowed in the declaration file, because a namespace is an object, so you can’t define it in the declaration file, but we can define the namespace as an environment value.

Also, when the namespace is defined in the declaration file, the export keyword is not needed, and all types inside the namespace are implicitly exported. in addition, you cannot export a value from the namespace, only the type is allowed to be exported.

VII. Type Extensions

When typescript foresees multiple interface types with the same name, it automatically merges these interface types into a single declaration.

Namespace has similar behavior. When declaring an existing namespace, typescript will automatically extend the existing namespace. So when interfaces and namespaces are in the global environment, they can be redeclared for the purpose of extending them.

Update the declaration file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// types/common/main.d.ts
/// <reference path="./interfaces.d.ts" />
/// <reference path="./functions.d.ts" />

--------------------------------------------------------
// types/common/interfaces.d.ts
interface Person {
    firstName: string;
    lastName: string;
    age: number;
}

--------------------------------------------------------
// types/common/functions.d.ts
declare namespace common {
    type GetFullName = ( p: Person ) => string;
}

Update program.ts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
interface Person {
    email: string;
}

interface Number {
    isEven(): boolean;
}

const ross: Person = {
    firstName: "Ross",
    lastName: "Geller",
    age: 29,
    email: "ross@geller.com"
};

const isAgeEven = ross.age.isEven();

console.log("isAgeEven", isAgeEven);

At this point typescript does not report an error. The Person interface and the Number (built-in type) interface have been extended in program.ts.

However, this does not apply to the module system; values and types can only be shared in script files, which are files that do not contain the import and export statements.

You can add a line of code export {} to the end of the program.ts file. At this point typescript should give the following error message.

1
2
3
4
Type '{ firstName: string; lastName: string; age: number; email: string; }' is not assignable to type 'Person'.
Object literal may only specify known properties, and 'firstName' does not exist in type 'Person'.ts(2322)

Property 'age' does not exist on type 'Person'.ts(2339)

Because adding the export {} code converts the program.ts file into a module, in this case typeScript does not extend the type in the global environment, but redeclares the type within the current module.

TypeScript provides a declare global environment declaration statement, which can be used to add declarations to the global scope within a module.

To update program.ts, use the declare global statement.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
declare global {
    // 扩展全局Person接口
    interface Person {
        email: string;
    }
}

declare global {
    // 扩展全局Number接口
    interface Number {
        isEven(): boolean;
    }

    var version: string;
}

const ross: Person = {
    firstName: 'Ross',
    lastName: 'Geller',
    age: 29,
    email: 'ross@geller.com'
}

const isAgeEven = ross.age.isEven();

console.log( 'isAgeEven', isAgeEven );

export {}

This time typescript does not report an error and successfully extends the global type within the module.