TypeScriptSeries of advanced :(6)Namespaces

Prior to TypeScript 1.5, there were concepts of Internal modules(declared as module {}) and External modules. From version 1.5 onwards, the naming of these two concepts has changed. Internal modules were replaced with Namespaces, namespace {}, and External modules were replaced with modules. With namespaces, you can define visible/invisible types or values independently, which can greatly avoid global naming conflicts. We use the export keyword to expose the corresponding type/value.

[toc]

One, to provide the officialValidatorsAs an example to experienceNamespaces

namespace Validation {
  // The type/value exposed by export can be accessed outside namespaace
  // Expose an interface that contains a method signature
  export interface StringValidator {
    isAcceptable(s: string) :boolean;
  }
  // Do not use export to export, can only be accessed inside namespace
  const lettersRegexp = /^[A-Za-z]+$/;
  const numberRegexp = / ^ [0-9] + $/;
  // Expose two classes
  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); }}}// Test case
let strings = ["Hello"."98052"."101"];
// String index signature initializes variables
let validators: { [s: string]: Validation.StringValidator } = {};
// Add two attributes, one for a Validator instance
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
    
// Start testing
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

Don’t understand why namespaces are used in the first place. Why not use a simple object? After reading the official example, I found that the type or value is freely defined in the namespace, and the content that is not exported becomes private.

Multifile namespaces

In a previous installment in the TypeScript series, I talked about namespace declaration merging, which makes multi-file namespaces possible. As we know, as the size of the project increases and the number of files increases, the contents of namespaces become larger. It is not wise to put everything in a namespace file, thus creating cross-file problems with namespaces. TS supports splitting the same namespace into multiple files while maintaining the same usage as if it were defined in the same file.



This article focuses only on the contents of a namespace. Let’s continue with the official example of splitting the chestnuts of the above Validator into multiple files.

  • First of all,Split BaseIn thevalidation.tsDefines the underlying namespace inValidation:
// validation.ts
namespace Validation {
    export interface StringValidator {
        isAcceptable(X:string) :boolean; }}Copy the code
  • Split alphanumerical validator partsIn:lettersOnlyValidator.ts, in theThe file beginsUse the triple slash instruction to introducevalidation.tsAnd then declare the merger:
// Use the triple slash directive to import validation.ts
/// <reference path="Validation.ts" />

// // Extend validation.ts
namespace Validation {
  // Private member, visible only in the current namaspace,
  // External or even other namespaces with the same name are not visible
  const lettersRegexp = /^[A-Za-z]+$/;
  // Export the shared member
  export class LettersOnlyValidator implements StringValidator {
    isAcceptable(s: string) {
      returnlettersRegexp.test(s); }}}Copy the code
  • Continue to expandIn:ZipCodeValidator.ts, pass at the beginning of the fileTriple slash instructionTo introduceValidation.ts:
// 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

Our namespace Validation is now merged, but note that when we use the corresponding export member in another file, we still use the triple slash command to import the related namespace file, as in the test.ts file:

// test.ts
// Use the triple slash command to import the related namespace file
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />

// The following usage is the same as without the split in the beginning
// Test case
let strings = ["Hello"."98052"."101"];
// Variable declaration, index signature
let validators: { [s: string]: Validation.StringValidator } = {};
// Add a Validator instance
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

// Start testing
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

Visible, we split a messy namespace into different files according to the function point, but in addition to the need to use the three slash command to introduce, still keep the same use method, later maintenance is much more convenient!

Of course, there are multiple files involved now, so we need to make sure that all the compiled code is loaded. There are two main ways:

  • Single file output:

    Configure the outputFile item to compile and output the specified file (including its dependent files) as a single JS file.

    npx tsc --outFile sample.js test.ts
    Copy the code

    As shown above, this command compiles the output of the test.ts file and the three files it imported through the triple slash directive in the order they were imported into a sample.js file, so that when test.ts is loaded, all compiled code is ensured to be loaded. We could also manually enumerate the corresponding files to compile the output, but this would obviously be more troublesome:

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

    In fact, in modern envy, we tend to use frameworks such as Vue or React, and in the configuration files of these projects, the output is often already configured for us as a single JS file.

  • Multi-file output:

    Multiple file output is the default option. Make each compiled file output a corresponding JS file separately. to introduce the corresponding JS file.

    <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

Three, alias,

As you may have noticed, we need to take the name of the namespace itself when we access the members exposed in the namespace externally, and despite the limitations of modern editors, we still want something simpler. Fortunately, TS provides us with a syntax to import newName = X.Y.Z to alias a namespace exposed member. Note that this is not to be confused with the module’s import syntax import q = require(X), the two are not the same thing. The namespace import newName = X.Y.X simply aliases a member of the namespace.

// The first nested namespace of the official example
namespace Shapes {
  const age = 18
  export const getAge = () = > age
  export namespace Polygons {
    export class Triangle {}
    export class Square {}}}// You can alias general members
import age = Shapes.getAge
// You can also alias a nested namespace
import polygons = Shapes.Polygons;
let sq = new polygons.Square(); // Same as 'new Shapes.polygons.square ()'
Copy the code

Environment namespacesAmbient Namespaces

This section is a bit of a dig, because it involves the ambient’ ambient’, we usually call declarations that do not define implementations ‘ambient’, and they tend to appear in the.d.ts extension file (decided, we’ll cover this in the next installment). We know that some libraries are not written in TS, but in JS, so we need to declare the API exposed by these libraries. Most JS libraries expose a top-level object, so namespace is a good choice. The following uses the D3 library as an example to define the shape of a third-party library in the environment namespace:

// Notice that the declaration of 'environment' requires the declare keyword
// Declare no interface to be implemented
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 no assigned value
declare var d3: D3.Base;
Copy the code

Is the environment namespace confusing? It doesn’t matter, the next article, in the wind and rain, the statement file waiting for you!