May 07, 2021 TypeScript
It is difficult to organize modules to provide the APIs you want to be consistent.
For example, you might want a module that can create different types with or
new
expose different naming types at different levels, and have some properties on the module object.
After reading this designation, you'll learn that if you write complex claims files that expose friendly APIs. This is specified for module (UMD) libraries because their choices have higher variability.
If you understand some of the core concepts about how TypeScript works, you can write declaration files for any structure.
If you're reading this guide, you probably know what type of type means in TypeScript. To be clear, the type is introduced by:
type sn = number | string;
)
interface I { x: number[]; }
)
class C { }
enum E { A, B, C }
import
declaration
Each of these declarations creates a new type name.
You may already understand what a value is compared to a type. A
value is a runtime name that can be referenced in an expression. F
or
let x = 5;
Create a value
x
x.
Similarly, you can create values in the following ways:
let
const
and
var
declarations
namespace
module
value
enum
statement
class
declaration
import
the value
function
declaration
Types can exist
in the namespace.
For example, if there
let x: A.B.C
we think that the
C
type comes from
A.B
namespace of A.
This distinction is subtle but important -- here,
A.B
a required type or value.
Given a name
A
we can find three different meanings: a type, a value, or a namespace. H
ow the name is parsed depends on what context it is in. F
or example, in the
let m: A.A = A;
A
A
first used as a namespace, then as a type name, and finally as a value.
These meanings may end up pointing to completely different statements!
It may seem confusing, but it's convenient as long as we don't overload it too much. Let's take a look at some useful combination behaviors.
Sharp-eyed readers may notice, for example,
class
in
both the
type and
value
lists.
class C { }
creates two things: type C points
to
C
instance structure of the class, and value C
points
C
to the class constructor.
Enumeral declarations have similar behavior.
Let's say we wrote
foo.d.ts
:
export var SomeVar: { a: SomeType };
export interface SomeType {
count: number;
}
Use it this way:
import * as foo from './foo';
let x: foo.SomeType = foo.SomeVar.a;
console.log(x.count);
It works well, but we know
SomeType
SomeVar
so we want them to have the same name.
We can use a combination to represent these two
Bar
objects (values and objects) by the same name Bar:
export var Bar: { a: Bar };
export interface Bar {
count: number;
}
This provides an opportunity to understand the use of the structure:
import { Bar } from './foo';
let x: Bar = Bar.a;
console.log(x.count);
Again, here we use
Bar
the type and value.
Note that we did
Bar
the Bar value to
Bar
type -- they are independent.
There are some claims that can be combined through multiple claims.
For example,
class C { }
interface C s can
interface C { }
at the same time and can both be properties of
C
It is legal as long as there is no conflict.
A common rule is that values always conflict with other values with the same name unless they are in different namespaces, and type conflicts occur in cases where
type s = string
and the namespace never conflicts.
Let's see how to use it.
interface
We can use one
interface
to add additional
interface
another interface declaration:
interface Foo {
x: number;
}
// ... elsewhere ...
interface Foo {
y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK
This also applies to classes:
class Foo {
x: number;
}
// ... elsewhere ...
interface Foo {
y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK
Note that we can't use the interface to add members to the type
type s = string;
)
namespace
namespace
can be used to add new types, values, and namespaces as long as there are no conflicts.
For example, we might add static members to a class:
class C {
}
// ... elsewhere ...
namespace C {
export let x: number;
}
let y = C.x; // OK
Note that in this example, we add a value to
C
part
of
C (its constructor).
Here because we added a
value,
and the container for the other values is another value (the type is contained in the namespace, and the namespace is contained in another namespace).
We can also add a namespace type to the class:
class C {
}
// ... elsewhere ...
namespace C {
export interface D { }
}
let y: C.D; // OK
In this example, namespace
namespace
we wrote the namespace
C
C as a namespace
C
not conflict with the value C or
C
C
the class.
Finally, we can make different merges through
namespace
F
inally, we could perform many different merges using
namespace
declarations.
This isn't a particularly realistic example, but shows all sorts of interesting behavior:
namespace X {
export interface Y { }
export class Z { }
}
// ... elsewhere ...
namespace X {
export var Y: number;
export namespace Z {
export class C { }
}
}
type X = string;
In this example, the first block of code creates the following name and meaning:
X
namespace
declaration contains a value,
Z
X
namespace
declaration contains a value,
Z
X
Y
Z
in
X
(instance structure of class)
X
value of value
Z
(the constructor of the class)
The second block of code creates the following names and meanings:
Y
number
which is a
X
of the value X
Z
Z
which is
X
of the value X
X.Z
C
X.Z
of C for the value
C
X
export =
or
import
An important principle is that
export
and
import
export or import all meanings of
the target.