Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the simplest possible example of where `declare` keyword is useful in TypeScript?

Tags:

typescript

I've read docs (ie, this) and a few blog posts about declare keyword in TypeScript and I still don't understand it. What I would like is a clear example of its most simple usage.

Here is the line of reasoning I initially came up with to come up with this simplest example (which is likely incorrect):

Have a .js file export something (untyped because this is plain JS).

Have a .ts file import whatever the .js file exported.

What needs to be exported and imported (an object? a function?) for me to see TS show an error that could be fixed by using declare?

like image 343
evianpring Avatar asked Apr 26 '19 14:04

evianpring


People also ask

What is the use of declare keyword in TypeScript?

The declare keyword in TypeScript is used for the Ambient declaration of variables or for methods. Ambient Declarations is like an import keyword. Which tells the compiler that the source exists in another file.

What is declare module in TypeScript?

The TypeScript declares module is one of the modules and keyword it is used for to surround and define the classes, interfaces; variables are also declared it will not originate with the TypeScript like that module is the set of files that contains values, classes, functions/methods, keywords, enum all these contains ...

How do I create a TypeScript module?

We can create a module by using the export keyword and can use in other modules by using the import keyword. Modules import another module by using a module loader. At runtime, the module loader is responsible for locating and executing all dependencies of a module before executing it.


2 Answers

Murat Karagöz answer above pointed in the right direction, and this answer will provide actual code with the minimal example of where we would use declare.

Here is a very simple npm module: just one index.js file that exports an object with one method on it. There are no type declarations here because it's just JS.

const item = {
    price: 5,
    name: 'item1',
};

export const MyModuleObject = { method: () => item };

Here is a very simple TypeScript npm project, with one dependency: the JS project linked above. The latter is therefore an imported npm module with no typings. Here is the index.ts file in the TS project:

/// <reference path="index.d.ts"/>

import { MyModuleObject } from 'npmModule';

import { LocalModuleObject } from './module';

// Without the "reference path" line, TS complains in line 3 that it could not find a declaration file for 'npmModule'.
// In this case, the import has type any, so TS does not complain about the call below to an inexistent method.
// When we uncomment line 1, we get a TS error on line 8: property 'test' does not exist on type { method: ... }
MyModuleObject.test();


// TS complains that test does not exist on type { method: ... }
// Here we did not need to have a `declare` statement in a type definitions file for TS to know this because here TS is
// using contextual typing:
LocalModuleObject.test();

Below is the code for index.d.ts:

declare module "npmModule" {
    export const Item: {
        price: number,
        name: string
    }

    export const MyModuleObject: {
        method: () => Item
    }
}

And the code for ./module:

export const LocalModuleObject = { method: () => 10 };

How is this an example of why declare is used - I put this in the comments of index.ts, but let me explain it in more words. index.ts is importing an object from an external module (one that is in node_modules), and another object from a local module (./module.js). Both modules are exporting an object with one method called method on it. In index.ts I am calling an inexistent method test on each of these objects.

TS uses contextual typing on the import of the local module, so it knows that test doesn't exist on the object. This does not happen on the import of the object in the external module: this import is imported with type any. Therefore, TS does not complain about the call to the inexistent method test. It does, however, complain that there are no typings for the external module, so this is a hint that implicit any is being used.

We can remedy this latter complaint by defining an index.d.ts that provides typings for the external library. This is where declare module is used: it declares what it is that the module npmModule exports; npmModule is the external import. In index.ts we must add the line /// <reference path="index.d.ts"/> so TS knows where to look for types.

like image 20
evianpring Avatar answered Nov 11 '22 18:11

evianpring


Your example is correct. For example you are using a node module which is written in plain javascript (no typings available), so the tscompiler will notice that (since it searches for the typings which are usually in the node module or an extra @typings/package).

You can however provide those typings yourself by telling the tscompiler in the tsconfig.json to look at file xyz.d.ts for the typings e.g.

tsconfig.json
{
  "files": [
    "typings/index.d.ts"
  ]
}

The index.d.ts is where all your custom typings are gathered which can look like this

index.d.ts
/// <reference path="custom-typings.d.ts" />

and the custom-typings.d.ts has the actual typings in it. This is where the declare keyword comes into play

custom-typings.d.ts
declare module "the-untyped-node-module-name" {
    export default class TheNodeModuleClass { 
        static showNotification(string: any): void;
    }
}

Now the Typescript Compiler knows there is a TheNodeModuleClass in the-untyped-node-module-name which has the static function showNotification.

For more info See Typscript Modules.


This is one use case for the keyword declare. There are of course more of it like declare var, declare function, declare class and so on.

like image 108
Murat Karagöz Avatar answered Nov 11 '22 20:11

Murat Karagöz