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
?
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.
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 ...
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With