“This is the 13th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021”

The namespace

introduce

This article describes how to organize your code in TypeScript using namespaces (formerly called “internal modules”). “Internal modules” are now called “namespaces”. Also, any place where the module keyword is used to declare an internal module should be replaced with the namespace keyword. This avoids confusing new users with similar names

The first step

Let’s start by writing a program that will use this example throughout this article. Let’s define a few simple string validators, assuming you’ll use them to validate user input in a form or to validate external data

All validators are placed in one file

interface StringValidator {
    isAcceptable(s: string) :boolean;
}

let lettersRegexp = /^[A-Za-z]+$/;
let numberRegexp = / ^ [0-9] + $/;

class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
        returnlettersRegexp.test(s); }}class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5&& numberRegexp.test(s); }}// Some samples to try
let strings = ["Hello"."98052"."101"];

// Validators to use
let validators: { [s: string]: StringValidator; } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();

// Show whether each string passed each validator
for (let s of strings) {
    for (let name in validators) {
        let isMatch = validators[name].isAcceptable(s);
        console.log(` '${ s }' ${ isMatch ? "matches" : "does not match" } '${ name }'. `); }}Copy the code

The namespace

As more validators are added, we need a way to organize our code so that we can record their types without worrying about naming conflicts with other objects. Therefore, we wrap the validators in a namespace rather than putting them in a global namespace

In the following example, we put all the validator-related types into a namespace called Validation. Because we want these interfaces and classes to be accessible outside of the namespace, we need to use export. In contrast, the variables lettersRegexp and numberRegexp are implementation details that do not need to be exported, so they are not accessible outside of the namespace. The test code at the end of the file, because it is outside the namespace to visit, so you need to limit the name of the type, such as Validation. LettersOnlyValidator

Use validators for namespaces

namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string) :boolean;
    }

    const lettersRegexp = /^[A-Za-z]+$/;
    const numberRegexp = / ^ [0-9] + $/;

    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            returnlettersRegexp.test(s); }}export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5&& numberRegexp.test(s); }}}// Some samples to try
let strings = ["Hello"."98052"."101"];

// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

// Show whether each string passed each validator
for (let s of strings) {
    for (let name in validators) {
        console.log(`"${ s }"-${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`); }}Copy the code

Separate to multiple files

As the application gets bigger and bigger, we need to separate the code into different files for easy maintenance

Namespaces in multiple files

Now we split the Validation namespace into multiple files. Although they are different files, they are still the same namespace and are used as if they were defined in a single file. Because there are dependencies between different files, we add reference tags to tell the compiler about the relationships between files. Our test code remains the same

Validation.ts

namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string) :boolean; }}Copy the code

LettersOnlyValidator.ts

/// <reference path="Validation.ts" />
namespace Validation {
    const lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            returnlettersRegexp.test(s); }}}Copy the code

ZipCodeValidator.ts

/// <reference path="Validation.ts" />
namespace Validation {
    const numberRegexp = / ^ [0-9] + $/;
    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5&& numberRegexp.test(s); }}}Copy the code

Test.ts

/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />

// Some samples to try
let strings = ["Hello"."98052"."101"];

// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

// Show whether each string passed each validator
for (let s of strings) {
    for (let name in validators) {
        console.log(`"${ s }"-${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`); }}Copy the code

When multiple files are involved, we must ensure that all compiled code is loaded. We can do this in two ways

The first way to compile all the input files into one output file is to use the –outFile flag:

tsc --outFile sample.js Test.ts
Copy the code

The compiler automatically sorts the output by reference tags in the source code. You can also specify each file individually

tsc --outFile sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts
Copy the code

Second, we can compile each file (the default), so each source file generates a JavaScript file. Then, import all the generated JavaScript files in the correct order on the page via the

MyTestPage.html (excerpt)

    <script src="Validation.js" type="text/javascript" />
    <script src="LettersOnlyValidator.js" type="text/javascript" />
    <script src="ZipCodeValidator.js" type="text/javascript" /> <script src="Test.js" type="text/javascript" /> Copy the code

The alias

Another way to simplify namespace manipulation is to give commonly used objects short names using import q = X.Y.Z. Not to be confused with the import x = require(‘name’) syntax used to load modules, which creates an alias for the specified symbol. You can use this method to create aliases for arbitrary identifiers, including objects in imported modules

namespace Shapes {
    export namespace Polygons {
        export class Triangle {}export class Square {}}}import polygons = Shapes.Polygons;
let sq = new polygons.Square(); // Same as "new Shapes.Polygons.Square()"
Copy the code

Note that instead of using the require keyword, we assign directly using the qualified name of the import symbol. This is similar to using VAR, but it also applies to types and imported symbols with namespace meaning. Importantly, for values, import generates a different reference than the original symbol, so changing the var value of the alias does not affect the value of the original variable

Use other JavaScript libraries

To describe the types of libraries that are not written in TypeScript, we need to declare the API that the library exports. Since most libraries provide only a few top-level objects, namespaces are a good way to represent them

We call it a declaration because it is not a concrete implementation of an external program. We usually write these declarations in.d.ts. If you are familiar with C/C++, you can think of them as.h files. Let’s look at some examples

External namespace

The popular library D3 defines its functionality in the global object D3. Because the library is loaded through a

D3.d.ts (Excerpts)

declare namespace D3 {
    export interface Selectors {
        select: {
            (selector: string): Selection;
            (element: EventTarget): Selection;
        };
    }

    export interface Event {
        x: number;
        y: number;
    }

    export interface Base extends Selectors {
        event: Event; }}declare var d3: D3.Base;
Copy the code

Advanced use of namespaces and modules

introduce

Here’s how you organize your code in TypeScript using modules and namespaces. We’ll also cover advanced usage scenarios for namespaces and modules, and common pitfalls in using them

Using namespaces

A namespace is a normal JavaScript object with a name that resides in a global namespace. This makes the namespace very easy to use. They can be used simultaneously in multiple files and are combined by –outFile. Namespaces are a great way to help you organize your Web applications. You can put all your dependencies in

But like other global namespace contamination, it can be difficult to identify dependencies between components, especially in large applications

Use the module

Like namespaces, modules can contain code and declarations. The difference is that a module can declare its dependencies

Modules add dependencies to module loaders (such as CommonJs/require.js). This may not be necessary for small JS applications, but for large applications, this small cost can lead to long-term modularity and maintainability benefits. Modules also provide better code reuse, greater closure, and better use of tools for optimization

Modules are the default and recommended way to organize code for Node.js applications

As of ECMAScript 2015, modules are built into the language and should be supported by all normal interpretation engines. Therefore, modules are recommended as a way to organize code for new projects

Namespaces and module pitfalls

In this section we will describe common namespaces and module pitfalls and how to avoid them

Use with modules/// <reference>

A common mistake is to use ///

to reference a module file. Import should be used instead. To understand the difference, we should first understand how the compiler uses import paths (for example, import x from “…”). ; Import x = require(“…” ) Inside… , etc.) to locate module type information.

The compiler first tries to find.ts,.tsx, or.d.ts in the corresponding path. If none of these files are found, the compiler looks for external module declarations. Recall that they were declared in the.d.ts file

  • myModules.d.ts
// In a .d.ts file or .ts file that is not a module:
declare module "SomeModule" {
    export function fn() :string;
}
Copy the code
  • myOtherModule.ts
/// <reference path="myModules.d.ts" />
import * as m from "SomeModule";
Copy the code

The reference label here specifies the location of the foreign module. This is how some TypeScript examples refer to Node.d. ts

Unnecessary namespaces

If you wanted to convert a namespace to a module, it might look like this:

  • shapes.ts
export namespace Shapes {
    export class Triangle { / *... * / }
    export class Square { / *... * /}}Copy the code

The top module Shapes wrap Triangle and Square. It’s confusing and annoying to the people who use it:

  • shapeConsumer.ts
import * as shapes from "./shapes";
let t = new shapes.Shapes.Triangle(); // shapes.Shapes?
Copy the code

One thing about modules in TypeScript is that different modules never use the same name in the same scope. Because the people who use modules name them, there is no need to wrap exported symbols in a namespace

Again, you should not use namespaces for modules, they are used to provide logical grouping and avoid naming conflicts. The module file itself is already a logical grouping, and its name is specified by the code importing the module, so there is no need to add an additional module layer to the exported object

Here are some examples of improvements:

  • shapes.ts
export class Triangle { / *... * / }
export class Square { / *... * / }
Copy the code
  • shapeConsumer.ts
import * as shapes from "./shapes";
let t = new shapes.Triangle();
Copy the code

Choice of modules

Just as every JS file corresponds to a module, module files in TypeScript correspond to generated JS files. This has the effect that, depending on the target module system you specify, you may not be able to connect to multiple module source files. For example, the outFile option cannot be used when the target module is commonJS or UMD, but outFile can be used when the target module is AMD or System in TypeScript 1.8 and later