A partir de ECMAScript 2015, JavaScript tiene un concepto de módulos. TypeScript comparte este concepto.
Los módulos se ejecutan dentro de su propio ámbito, no en el ámbito global; esto significa que las variables, funciones, clases, etc. declaradas en un módulo no son visibles fuera del módulo a menos que se exporten explícitamente usando uno de los export
formas. Por el contrario, para consumir una variable, función, clase, interfaz, etc.exportada desde un módulo diferente, debe importarse utilizando uno de los import
formas.
Los módulos son declarativos; las relaciones entre los módulos se especifican en términos de importaciones y exportaciones a nivel de archivo.
Los módulos se importan entre sí mediante un cargador de módulos. En tiempo de ejecución, el cargador de módulos es responsable de localizar y ejecutar todas las dependencias de un módulo antes de ejecutarlo. Los cargadores de módulos conocidos que se utilizan en JavaScript son el cargador de Node.js para CommonJS módulos y el RequireJS cargador para AMD módulos en aplicaciones web.
En TypeScript, al igual que en ECMAScript 2015, cualquier archivo que contenga un nivel superior import
o export
se considera un módulo. Por el contrario, un archivo sin ningún nivel superior import
o export
Las declaraciones se tratan como un script cuyo contenido está disponible en el ámbito global (y por lo tanto también en los módulos).
Exportar
Exportar una declaración
Cualquier declaración (como una variable, función, clase, alias de tipo o interfaz) se puede exportar agregando el export
palabra clave.
StringValidator.ts
export interface StringValidator { isAcceptable(s: string): boolean; }
ZipCodeValidator.ts
import { StringValidator } from "./StringValidator"; export const numberRegexp = /^[0-9]+$/; export class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } }
Exportar declaraciones
Las declaraciones de exportación son útiles cuando es necesario cambiar el nombre de las exportaciones para los consumidores, por lo que el ejemplo anterior se puede escribir como:
class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } } export { ZipCodeValidator }; export { ZipCodeValidator as mainValidator };
Reexportaciones
A menudo, los módulos amplían otros módulos y exponen parcialmente algunas de sus características. Una reexportación no lo importa localmente ni introduce una variable local.
ParseIntBasedZipCodeValidator.ts
export class ParseIntBasedZipCodeValidator { isAcceptable(s: string) { return s.length === 5 && parseInt(s).toString() === s; } } // Export original validator but rename it export { ZipCodeValidator as RegExpBasedZipCodeValidator } from "./ZipCodeValidator";
Opcionalmente, un módulo puede envolver uno o más módulos y combinar todas sus exportaciones usando export * from "module"
sintaxis.
AllValidators.ts
export * from "./StringValidator"; // exports 'StringValidator' interface export * from "./ZipCodeValidator"; // exports 'ZipCodeValidator' class and 'numberRegexp' constant value export * from "./ParseIntBasedZipCodeValidator"; // exports the 'ParseIntBasedZipCodeValidator' class // and re-exports 'RegExpBasedZipCodeValidator' as alias // of the 'ZipCodeValidator' class from 'ZipCodeValidator.ts' // module.
Importar
Importar es tan fácil como exportar desde un módulo. La importación de una declaración exportada se realiza mediante uno de los import
formularios a continuación:
Importar una sola exportación de un módulo
import { ZipCodeValidator } from "./ZipCodeValidator"; let myValidator = new ZipCodeValidator();
las importaciones también se pueden renombrar
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator"; let myValidator = new ZCV();
Importe todo el módulo en una sola variable y utilícelo para acceder a las exportaciones del módulo
import * as validator from "./ZipCodeValidator"; let myValidator = new validator.ZipCodeValidator();
Importar un módulo solo para efectos secundarios
Aunque no es una práctica recomendada, algunos módulos configuran un estado global que pueden utilizar otros módulos. Estos módulos pueden no tener exportaciones o el consumidor no está interesado en ninguna de sus exportaciones. Para importar estos módulos, use:
import "./my-module.js";
Importación de tipos
Antes de TypeScript 3.8, puede importar un tipo usando import
. Con TypeScript 3.8, puede importar un tipo usando el import
declaración, o usando import type
.
// Re-using the same import import { APIResponseType } from "./api"; // Explicitly use import type import type { APIResponseType } from "./api";
import type
siempre se garantiza que se eliminará de su JavaScript, y herramientas como Babel pueden hacer mejores suposiciones sobre su código a través del isolatedModules
bandera del compilador. Puedes leer más en el 3.8 notas de la versión.
Exportaciones predeterminadas
Cada módulo puede exportar opcionalmente un default
exportar. Las exportaciones predeterminadas están marcadas con la palabra clave default
; y solo puede haber uno default
exportar por módulo. default
las exportaciones se importan utilizando un formulario de importación diferente.
default
las exportaciones son realmente útiles. Por ejemplo, una biblioteca como jQuery puede tener una exportación predeterminada de jQuery
o $
, que probablemente también importaríamos con el nombre $
o jQuery
.
JQuery.d.ts
declare let $: JQuery; export default $;
App.ts
import $ from "jquery"; $("button.continue").html("Next Step...");
Las clases y declaraciones de funciones se pueden crear directamente como exportaciones predeterminadas. Los nombres de declaración de funciones y clases de exportación predeterminados son opcionales.
ZipCodeValidator.ts
export default class ZipCodeValidator { static numberRegexp = /^[0-9]+$/; isAcceptable(s: string) { return s.length === 5 && ZipCodeValidator.numberRegexp.test(s); } }
Test.ts
import validator from "./ZipCodeValidator"; let myValidator = new validator();
o
StaticZipCodeValidator.ts
const numberRegexp = /^[0-9]+$/; export default function (s: string) { return s.length === 5 && numberRegexp.test(s); }
Test.ts
import validate from "./StaticZipCodeValidator"; let strings = ["Hello", "98052", "101"]; // Use function validate strings.forEach((s) => { console.log(`"${s}" ${validate(s) ? "matches" : "does not match"}`); });
default
las exportaciones también pueden ser solo valores:
OneTwoThree.ts
export default "123";
Log.ts
import num from "./OneTwoThree"; console.log(num); // "123"
Exportar todo como x
Con TypeScript 3.8, puede utilizar export * as ns
como una forma abreviada de volver a exportar otro módulo con un nombre:
export * as utilities from "./utilities";
Esto toma todas las dependencias de un módulo y lo convierte en un campo exportado, podría importarlo así:
import { utilities } from "./index";
export =
y import = require()
Tanto CommonJS como AMD generalmente tienen el concepto de un exports
objeto que contiene todas las exportaciones de un módulo.
También apoyan la sustitución del exports
objeto con un único objeto personalizado. Las exportaciones predeterminadas están destinadas a reemplazar este comportamiento; sin embargo, los dos son incompatibles. Soporta TypeScript export =
para modelar el flujo de trabajo tradicional de CommonJS y AMD.
los export =
la sintaxis especifica un único objeto que se exporta desde el módulo. Puede ser una clase, interfaz, espacio de nombres, función o enumeración.
Al exportar un módulo usando export =
, Específico de TypeScript import module = require("module")
debe utilizarse para importar el módulo.
ZipCodeValidator.ts
let numberRegexp = /^[0-9]+$/; class ZipCodeValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } } export = ZipCodeValidator;
Test.ts
import zip = require("./ZipCodeValidator"); // Some samples to try let strings = ["Hello", "98052", "101"]; // Validators to use let validator = new zip(); // Show whether each string passed each validator strings.forEach((s) => { console.log( `"${s}" - ${validator.isAcceptable(s) ? "matches" : "does not match"}` ); });
Generación de código para módulos
Dependiendo del destino del módulo especificado durante la compilación, el compilador generará el código apropiado para Node.js (CommonJS), require.js (AMD), UMD, SystemJS, o Módulos nativos de ECMAScript 2015 (ES6) sistemas de carga de módulos. Para obtener más información sobre lo que define
, require
y register
las llamadas en el código generado sí, consulte la documentación de cada cargador de módulo.
Este sencillo ejemplo muestra cómo los nombres utilizados durante la importación y exportación se traducen al código de carga del módulo.
SimpleModule.ts
import m = require("mod"); export let t = m.something + 1;
AMD / RequireJS SimpleModule.js
define(["require", "exports", "./mod"], function (require, exports, mod_1) { exports.t = mod_1.something + 1; });
CommonJS / Node SimpleModule.js
var mod_1 = require("./mod"); exports.t = mod_1.something + 1;
UMD SimpleModule.js
(function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports", "./mod"], factory); } })(function (require, exports) { var mod_1 = require("./mod"); exports.t = mod_1.something + 1; });
Sistema SimpleModule.js
System.register(["./mod"], function (exports_1) { var mod_1; var t; return { setters: [ function (mod_1_1) { mod_1 = mod_1_1; }, ], execute: function () { exports_1("t", (t = mod_1.something + 1)); }, }; });
Módulos nativos de ECMAScript 2015 SimpleModule.js
import { something } from "./mod"; export var t = something + 1;
Ejemplo simple
A continuación, hemos consolidado las implementaciones de Validator utilizadas en ejemplos anteriores para exportar solo una exportación con nombre de cada módulo.
Para compilar, debemos especificar un destino de módulo en la línea de comando. Para Node.js, use --module commonjs
; para require.js, usa --module amd
. Por ejemplo:
tsc --module commonjs Test.ts
Cuando se compile, cada módulo se convertirá en un .js
expediente. Al igual que con las etiquetas de referencia, el compilador seguirá import
declaraciones para compilar archivos dependientes.
Validation.ts
export interface StringValidator { isAcceptable(s: string): boolean; }
LettersOnlyValidator.ts
import { StringValidator } from "./Validation"; const lettersRegexp = /^[A-Za-z]+$/; export class LettersOnlyValidator implements StringValidator { isAcceptable(s: string) { return lettersRegexp.test(s); } }
ZipCodeValidator.ts
import { StringValidator } from "./Validation"; const numberRegexp = /^[0-9]+$/; export class ZipCodeValidator implements StringValidator { isAcceptable(s: string) { return s.length === 5 && numberRegexp.test(s); } }
Test.ts
import { StringValidator } from "./Validation"; import { ZipCodeValidator } from "./ZipCodeValidator"; import { LettersOnlyValidator } from "./LettersOnlyValidator"; // 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 strings.forEach((s) => { for (let name in validators) { console.log( `"${s}" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${name}` ); } });
Carga de módulos opcionales y otros escenarios de carga avanzados
En algunos casos, es posible que desee cargar un módulo solo en determinadas condiciones. En TypeScript, podemos usar el patrón que se muestra a continuación para implementar este y otros escenarios de carga avanzados para invocar directamente los cargadores de módulos sin perder la seguridad de los tipos.
El compilador detecta si cada módulo se utiliza en el JavaScript emitido. Si un identificador de módulo solo se usa como parte de anotaciones de tipo y nunca como una expresión, entonces no require
se emite una llamada para ese módulo. Esta elisión de referencias no utilizadas es una buena optimización del rendimiento y también permite la carga opcional de esos módulos.
La idea central del patrón es que el import id = require("...")
declaración nos da acceso a los tipos expuestos por el módulo. Se invoca el cargador de módulos (a través de require
) dinámicamente, como se muestra en la if
cuadras abajo. Esto aprovecha la optimización de elisión de referencia para que el módulo solo se cargue cuando sea necesario. Para que este patrón funcione, es importante que el símbolo definido mediante un import
sólo se utiliza en posiciones de tipo (es decir, nunca en una posición que se emitiría en JavaScript).
Para mantener la seguridad de los tipos, podemos utilizar el typeof
palabra clave. los typeof
La palabra clave, cuando se usa en una posición de tipo, produce el tipo de un valor, en este caso el tipo del módulo.
Carga dinámica de módulos en Node.js
declare function require(moduleName: string): any; import { ZipCodeValidator as Zip } from "./ZipCodeValidator"; if (needZipValidation) { let ZipCodeValidator: typeof Zip = require("./ZipCodeValidator"); let validator = new ZipCodeValidator(); if (validator.isAcceptable("...")) { /* ... */ } }
Ejemplo: carga dinámica de módulos en require.js
declare function require( moduleNames: string[], onLoad: (...args: any[]) => void ): void; import * as Zip from "./ZipCodeValidator"; if (needZipValidation) { require(["./ZipCodeValidator"], (ZipCodeValidator: typeof Zip) => { let validator = new ZipCodeValidator.ZipCodeValidator(); if (validator.isAcceptable("...")) { /* ... */ } }); }
Ejemplo: carga dinámica de módulos en System.js
declare const System: any; import { ZipCodeValidator as Zip } from "./ZipCodeValidator"; if (needZipValidation) { System.import("./ZipCodeValidator").then((ZipCodeValidator: typeof Zip) => { var x = new ZipCodeValidator(); if (x.isAcceptable("...")) { /* ... */ } }); }
Trabajar con otras bibliotecas de JavaScript
Para describir la forma de las bibliotecas que no están escritas en TypeScript, necesitamos declarar la API que expone la biblioteca.
Llamamos “ambiente” a las declaraciones que no definen una implementación. Normalmente, estos se definen en .d.ts
archivos. Si está familiarizado con C / C ++, puede pensar en ellos como .h
archivos. Veamos algunos ejemplos.
Módulos ambientales
En Node.js, la mayoría de las tareas se realizan cargando uno o más módulos. Podríamos definir cada módulo en su propio .d.ts
archivo con declaraciones de exportación de nivel superior, pero es más conveniente escribirlos como uno más grande .d.ts
expediente. Para hacerlo, usamos una construcción similar a los espacios de nombres ambientales, pero usamos la module
palabra clave y el nombre entre comillas del módulo que estará disponible para una importación posterior. Por ejemplo:
node.d.ts (extracto simplificado)
declare module "url" { export interface Url { protocol?: string; hostname?: string; pathname?: string; } export function parse( urlStr: string, parseQueryString?, slashesDenoteHost? ): Url; } declare module "path" { export function normalize(p: string): string; export function join(...paths: any[]): string; export var sep: string; }
Ahora podemos /// <reference>
node.d.ts
y luego cargue los módulos usando import url = require("url");
o import * as URL from "url"
.
/// <reference path="node.d.ts"/> import * as URL from "url"; let myUrl = URL.parse("http://www.typescriptlang.org");
Módulos ambientales taquigráficos
Si no desea tomarse el tiempo para escribir declaraciones antes de usar un nuevo módulo, puede usar una declaración abreviada para comenzar rápidamente.
declaraciones.d.ts
declare module "hot-new-module";
Todas las importaciones de un módulo de taquigrafía tendrán la any
escribe.
import x, { y } from "hot-new-module"; x(y);
Declaraciones de módulos comodín
Algunos cargadores de módulos como SystemJS y AMD permitir que se importe contenido que no sea JavaScript. Estos suelen utilizar un prefijo o sufijo para indicar la semántica de carga especial. Las declaraciones de módulos comodín se pueden utilizar para cubrir estos casos.
declare module "*!text" { const content: string; export default content; } // Some do it the other way around. declare module "json!*" { const value: any; export default value; }
Ahora puedes importar cosas que coincidan "*!text"
o "json!*"
.
import fileContent from "./xyz.txt!text"; import data from "json!http://example.com/data.json"; console.log(data, fileContent);
Módulos UMD
Algunas bibliotecas están diseñadas para usarse en muchos cargadores de módulos o sin carga de módulos (variables globales). Estos se conocen como UMD módulos. Se puede acceder a estas bibliotecas mediante una importación o una variable global. Por ejemplo:
math-lib.d.ts
export function isPrime(x: number): boolean; export as namespace mathLib;
Luego, la biblioteca se puede usar como una importación dentro de los módulos:
import { isPrime } from "math-lib"; isPrime(2); mathLib.isPrime(2); // ERROR: can't use the global definition from inside a module
También se puede utilizar como variable global, pero solo dentro de un script. (Un script es un archivo sin importaciones ni exportaciones).
mathLib.isPrime(2);
Orientación para estructurar módulos
Exportar lo más cerca posible del nivel superior
Los consumidores de su módulo deben tener la menor fricción posible al usar los elementos que exporta. Agregar demasiados niveles de anidación tiende a ser engorroso, así que piense detenidamente cómo desea estructurar las cosas.
Exportar un espacio de nombres desde su módulo es un ejemplo de cómo agregar demasiadas capas de anidación. Si bien los espacios de nombres a veces tienen sus usos, agregan un nivel adicional de direccionamiento indirecto cuando se usan módulos. Esto puede convertirse rápidamente en un problema para los usuarios y, por lo general, no es necesario.
Los métodos estáticos en una clase exportada tienen un problema similar: la propia clase agrega una capa de anidamiento. A menos que aumente la expresividad o la intención de una manera claramente útil, considere simplemente exportar una función de ayuda.
Si solo está exportando una class
o function
, usar export default
Así como “exportar cerca del nivel superior” reduce la fricción en su los consumidores del módulo, también lo hace la introducción de una exportación predeterminada. Si el propósito principal de un módulo es albergar una exportación específica, entonces debería considerar exportarlo como una exportación predeterminada. Esto hace que tanto la importación como el uso real de la importación sean un poco más fáciles. Por ejemplo:
MyClass.ts
export default class SomeType { constructor() { ... } }
MyFunc.ts
export default function getThing() { return "thing"; }
Consumer.ts
import t from "./MyClass"; import f from "./MyFunc"; let x = new t(); console.log(f());
Esto es óptimo para los consumidores. Pueden nombrar tu tipo como quieran (t
en este caso) y no tiene que hacer un exceso de puntos para encontrar sus objetos.
Si está exportando varios objetos, colóquelos todos en el nivel superior
MisCosas.ts
export class SomeType { /* ... */ } export function someFunc() { /* ... */ }
A la inversa, al importar:
Enumere explícitamente los nombres importados
Consumer.ts
import { SomeType, someFunc } from "./MyThings"; let x = new SomeType(); let y = someFunc();
Utilice el patrón de importación del espacio de nombres si está importando una gran cantidad de cosas
MyLargeModule.ts
export class Dog { ... } export class Cat { ... } export class Tree { ... } export class Flower { ... }
Consumer.ts
import * as myLargeModule from "./MyLargeModule.ts"; let x = new myLargeModule.Dog();
Reexportar para ampliar
A menudo, necesitará ampliar la funcionalidad de un módulo. Un patrón JS común es aumentar el objeto original con extensiones, similar a cómo funcionan las extensiones de JQuery. Como mencionamos antes, los módulos no unir como lo harían los objetos de espacio de nombres globales. La solución recomendada es no mute el objeto original, sino que exporte una nueva entidad que proporcione la nueva funcionalidad.
Considere una implementación de calculadora simple definida en el módulo Calculator.ts
. El módulo también exporta una función auxiliar para probar la funcionalidad de la calculadora pasando una lista de cadenas de entrada y escribiendo el resultado al final.
Calculator.ts
export class Calculator { private current = 0; private memory = 0; private operator: string; protected processDigit(digit: string, currentValue: number) { if (digit >= "0" && digit <= "9") { return currentValue * 10 + (digit.charCodeAt(0) - "0".charCodeAt(0)); } } protected processOperator(operator: string) { if (["+", "-", "*", "https://foroayuda.es/"].indexOf(operator) >= 0) { return operator; } } protected evaluateOperator( operator: string, left: number, right: number ): number { switch (this.operator) { case "+": return left + right; case "-": return left - right; case "*": return left * right; case "https://foroayuda.es/": return left / right; } } private evaluate() { if (this.operator) { this.memory = this.evaluateOperator( this.operator, this.memory, this.current ); } else { this.memory = this.current; } this.current = 0; } public handleChar(char: string) { if (char === "=") { this.evaluate(); return; } else { let value = this.processDigit(char, this.current); if (value !== undefined) { this.current = value; return; } else { let value = this.processOperator(char); if (value !== undefined) { this.evaluate(); this.operator = value; return; } } } throw new Error(`Unsupported input: '${char}'`); } public getResult() { return this.memory; } } export function test(c: Calculator, input: string) { for (let i = 0; i < input.length; i++) { c.handleChar(input[i]); } console.log(`result of '${input}' is '${c.getResult()}'`); }
Aquí hay una prueba simple para la calculadora que usa los datos expuestos. test
función.
TestCalculator.ts
import { Calculator, test } from "./Calculator"; let c = new Calculator(); test(c, "1+2*33/11="); // prints 9
Ahora, para extender esto y agregar soporte para la entrada con números en bases distintas de 10, creemos ProgrammerCalculator.ts
ProgrammerCalculator.ts
import { Calculator } from "./Calculator"; class ProgrammerCalculator extends Calculator { static digits = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", ]; constructor(public base: number) { super(); const maxBase = ProgrammerCalculator.digits.length; if (base <= 0 || base > maxBase) { throw new Error(`base has to be within 0 to ${maxBase} inclusive.`); } } protected processDigit(digit: string, currentValue: number) { if (ProgrammerCalculator.digits.indexOf(digit) >= 0) { return ( currentValue * this.base + ProgrammerCalculator.digits.indexOf(digit) ); } } } // Export the new extended calculator as Calculator export { ProgrammerCalculator as Calculator }; // Also, export the helper function export { test } from "./Calculator";
El nuevo módulo ProgrammerCalculator
exporta una forma de API similar a la del original Calculator
módulo, pero no aumenta ningún objeto en el módulo original. Aquí hay una prueba para nuestra clase ProgrammerCalculator:
TestProgrammerCalculator.ts
import { Calculator, test } from "./ProgrammerCalculator"; let c = new Calculator(2); test(c, "001+010="); // prints 3
No use espacios de nombres en módulos
Cuando se cambia por primera vez a una organización basada en módulos, una tendencia común es envolver las exportaciones en una capa adicional de espacios de nombres. Los módulos tienen su propio alcance y solo las declaraciones exportadas son visibles desde fuera del módulo. Teniendo esto en cuenta, el espacio de nombres proporciona muy poco valor, si es que lo hay, cuando se trabaja con módulos.
En el frente de la organización, los espacios de nombres son útiles para agrupar objetos y tipos relacionados lógicamente en el ámbito global. Por ejemplo, en C #, encontrará todos los tipos de colección en System.Collections. Al organizar nuestros tipos en espacios de nombres jerárquicos, proporcionamos una buena experiencia de “descubrimiento” para los usuarios de esos tipos. Los módulos, por otro lado, ya están presentes en un sistema de archivos, necesariamente. Tenemos que resolverlos por ruta y nombre de archivo, por lo que hay un esquema de organización lógico que podemos usar. Podemos tener una carpeta / collections / generic / con un módulo de lista en ella.
Los espacios de nombres son importantes para evitar colisiones de nombres en el ámbito global. Por ejemplo, podrías tener My.Application.Customer.AddForm
y My.Application.Order.AddForm
: Dos tipos con el mismo nombre, pero con un espacio de nombres diferente. Sin embargo, esto no es un problema con los módulos. Dentro de un módulo, no hay ninguna razón plausible para tener dos objetos con el mismo nombre. Desde el punto de vista del consumo, el consumidor de cualquier módulo puede elegir el nombre que usará para referirse al módulo, por lo que los conflictos de nombres accidentales son imposibles.
Para obtener más información sobre módulos y espacios de nombres, consulte Espacios de nombres y módulos.
Banderas rojas
Todas las siguientes son señales de alerta para la estructuración de módulos. Vuelva a verificar que no está intentando asignar un espacio de nombres a sus módulos externos si alguno de estos se aplica a sus archivos:
- Un archivo cuya única declaración de nivel superior es
export namespace Foo { ... }
(retirarFoo
y mover todo ‘arriba’ un nivel) - Varios archivos que tienen el mismo
export namespace Foo {
en el nivel superior (no crea que estos se van a combinar en unoFoo
!)