Как использовать пространства имен с внешними модулями TypeScript?

у меня есть код:

базовые типы.ТС

export module Living.Things {
  export class Animal {
    move() { /* ... */ }
  }
  export class Plant {
    photosynthesize() { /* ... */ }
  }
}

собака.ТС

import b = require('./baseTypes');

export module Living.Things {
  // Error, can't find name 'Animal', ??
  export class Dog extends Animal {
    woof() { }
  }
}

дерево.ТС

// Error, can't use the same name twice, ??
import b = require('./baseTypes');
import b = require('./dogs');

module Living.Things {
  // Why do I have to write b.Living.Things.Plant instead of b.Plant??
  class Tree extends b.Living.Things.Plant {

  }
}

это все очень запутанно. Я хочу иметь кучу внешних модулей, все типы вкладов в одно и то же пространство имен, Living.Things. Кажется, что это вообще не работает - я не вижу Animal на dogs.ts. Я должен написать полное имя пространства имен b.Living.Things.Plant на tree.ts. Это не работает, чтобы объедините несколько объектов в одном пространстве имен в файле. Как мне это сделать?

8 ответов


Аналогия Чашки Конфеты

Версия 1: чашка для каждой конфеты

предположим, вы написали такой код:

в mod1.ТС

export namespace A {
    export class Twix { ... }
}

в mod2.ТС

export namespace A {
    export class PeanutButterCup { ... }
}

Mod3.ТС

export namespace A {
     export class KitKat { ... }
}

вы создали эту установку: enter image description here

каждый модуль (лист бумаги) получает своя чашка имени A. Это бесполезно - на самом деле ты не организации ваша конфета здесь, вы просто добавляете дополнительный шаг (вынимая его из чашки) между вами и угощениями.


Версия 2: Одна чашка в глобальном объеме

если бы вы не использовали модули, вы могли бы написать такой код (обратите внимание на отсутствие export объявления):

global1.ТС

namespace A {
    export class Twix { ... }
}

global2.ТС

namespace A {
    export class PeanutButterCup { ... }
}

global3.ТС

namespace A {
     export class KitKat { ... }
}

этой код создает объединенное пространство имен A в глобальном масштабе:

enter image description here

Эта настройка полезна, но не применяется в случае модулей (поскольку модули не загрязняют глобальный масштаб.)


версия 3: Going cupless

возвращаясь к исходному примеру, чашки A, A и A не делаю вам никаких одолжений. Вместо этого вы можете написать код как:

в mod1.ТС

export class Twix { ... }

в mod2.ТС

export class PeanutButterCup { ... }

Mod3.ТС

export class KitKat { ... }

чтобы создать изображение, которое выглядит как это:

enter image description here

намного лучше!

теперь, если вы все еще думаете о том, насколько вы действительно хотите использовать пространство имен с модулями, читайте дальше...


это не те понятия, которые вы ищете

нам нужно вернуться к истокам того, почему пространства имен существуют в первую очередь, и изучить, имеют ли эти причины смысл для внешних модули.

организация: пространства имен удобны для группировки логически связанных объектов и типов. Например, в C# вы найдете все типы коллекций в System.Collections. Организуя наши типы в иерархические пространства имен, мы обеспечиваем хороший опыт "обнаружения" для пользователей этих типов.

Конфликты Имен: пространства имен важны, чтобы избежать конфликтов имен. Например, у вас может быть My.Application.Customer.AddForm и My.Application.Order.AddForm -- два типа с одинаковым именем, но разным пространством имен. На языке, где все идентификаторы существуют в одной корневой области и все сборки загружают все типы, важно иметь все в пространстве имен.

имеют ли эти причины смысл во внешних модулях?

организация внешние модули уже присутствуют в файловой системе, обязательно. Мы должны решить их по пути и имени файла, поэтому для нас есть логическая схема организации использовать. У нас может быть С в нем.

Конфликты Имен: это не применяется вообще во внешних модулях. внутри модуль, нет никаких оснований, чтобы иметь два объекта с одинаковым именем. Со стороны потребления,потребитель любого данного модуля получает выбрать имя, которое они будут использовать для ссылки на модуль, поэтому случайные конфликты имен невозможны.


даже если вы не верите, что эти причины адекватно решаются тем, как работают модули, "решение" попытки использовать пространства имен во внешних модулях даже не работает.

коробки в коробках в коробках

история:

ваш друг Боб звонит вам. "У меня есть отличная новая схема организации в моем доме", - говорит он, - " приходите проверить это!". Ловкач, пойдем посмотрим, что придумал Боб.

вы начинаете на кухне и открыть кладовая. Есть 60 различных коробок, каждая с надписью "кладовая". Вы выбираете коробку наугад и открываете ее. Внутри одна коробка с надписью "зерна". Вы открываете коробку " зерна "и находите одну коробку с надписью"макароны". Вы открываете коробку " паста "и находите одну коробку с надписью"Пенне". Вы открываете эту коробку и находите, как и ожидали, пакет пасты пенне.

слегка запутавшись, вы берете соседнюю коробку, также помеченную "кладовая". Внутри одна коробка с надписью "зерна". Вы открываете коробка " зерна "и, опять же, найти одну коробку с надписью"макароны". Вы открываете коробку " паста "и находите одну коробку, на этой надпись"ригатони". Вы открываете эту коробку и находите... пакет пасты ригатони.

"это здорово!- говорит Боб. "Все находится в пространстве имен!".

один box вместо три. На самом деле, поскольку ваша кладовая уже отсортирована по полкам, вам вообще не нужны коробки. Почему бы просто не поставить пасту на полку и не забрать ее, когда она вам понадобится?"

ничего плохого в ответе Райана, но для людей, которые пришли сюда в поисках того, как поддерживать один класс-за-файлом структура при правильном использовании пространств имен ES6 обратитесь к этой полезный ресурс от Microsoft.

одна вещь, которая мне непонятна после чтения документа: как импортировать весь (объединенный) модуль с один import.

редактировать Возвращается, чтобы обновить этот ответ. Ля в TS появляется несколько подходов к пространству имен.

все классы модулей в одном файле.

export namespace Shapes {
    export class Triangle {}
    export class Square {}      
}

импортировать файлы в пространство имен и переназначить

import { Triangle as _Triangle } from './triangle';
import { Square as _Square } from './square';

export namespace Shapes {
  export const Triangle = _Triangle;
  export const Square = _Square;
}

бочки

// ./shapes/index.ts
export { Triangle } from './triangle';
export { Square } from './square';

// in importing file:
import * as Shapes from './shapes/index.ts';
// by node module convention, you can ignore '/index.ts':
import * as Shapes from './shapes';
let myTriangle = new Shapes.Triangle();

окончательное рассмотрение. Вы мог бы пространство имен каждый файл

// triangle.ts
export namespace Shapes {
    export class Triangle {}
}

// square.ts
export namespace Shapes {
    export class Square {}
}

но поскольку один импортирует два класса из одного пространства имен, TS будет жаловаться, что есть дубликат идентификатора. Единственное решение, как на этот раз, - это псевдоним пространство имен.

import { Shapes } from './square';
import { Shapes as _Shapes } from './triangle';

// ugh
let myTriangle = new _Shapes.Shapes.Triangle();

это сглаживание абсолютно отвратительно, поэтому не делайте этого. Вам лучше использовать подход сверху. Лично я предпочитаю "бочку".


попробуйте организовать по папке:

базовые типы.ТС

export class Animal {
    move() { /* ... */ }
}

export class Plant {
    photosynthesize() { /* ... */ }
}

собака.ТС

import b = require('./baseTypes');

export class Dog extends b.Animal {
    woof() { }
}   

дерево.ТС

import b = require('./baseTypes');

class Tree extends b.Plant {
}

LivingThings.ТС

import dog = require('./dog')
import tree = require('./tree')

export = {
    dog: dog,
    tree: tree
}

main.ТС

import LivingThings = require('./LivingThings');
console.log(LivingThings.Tree)
console.log(LivingThings.Dog)

идея заключается в том, что ваш модуль сам по себе не должен заботиться / знать, что они участвуют в пространстве имен, но это предоставляет ваш API потребителю компактным, разумным способом, который является агностиком того, какой тип модульной системы вы используете для проекта.


небольшое затруднение Альбинофренчи ответ:

базы.ТС

export class Animal {
move() { /* ... */ }
}

export class Plant {
  photosynthesize() { /* ... */ }
}

собака.ТС

import * as b from './base';

export class Dog extends b.Animal {
   woof() { }
} 

вещи.ТС

import { Dog } from './dog'

namespace things {
  export const dog = Dog;
}

export = things;

main.ТС

import * as things from './things';

console.log(things.dog);

несколько вопросов / комментариев, которые я видел вокруг этой темы, звучат для меня так, как будто человек использует Namespace где они означают "псевдоним модуля". Как отметил Райан Кавано в одном из своих комментариев, Вы можете повторно экспортировать несколько модулей модуля "обертки".

если вы действительно хотите импортировать все это из одного имени/псевдонима модуля, объедините модуль-оболочку с отображением путей в вашем tsconfig.json.

пример:

./path/to/CompanyName.Products/Foo.ts

export class Foo {
    ...
}


./path/to/CompanyName.Products/Bar.ts

export class Bar {
    ...
}


./path/to/CompanyName.Products/index.ts

export { Foo } from './Foo';
export { Bar } from './Bar';



tsconfig.json

{
    "compilerOptions": {
        ...
        paths: {
            ...
            "CompanyName.Products": ["./path/to/CompanyName.Products/index"],
            ...
        }
        ...
    }
    ...
}



main.ts

import { Foo, Bar } from 'CompanyName.Products'

Примечание: разрешение модуля на выходе .файлы js должны быть обработаны каким-то образом, например, с этим https://github.com/tleunen/babel-plugin-module-resolver

пример .babelrc разрешения псевдоним:

{
    "plugins": [
        [ "module-resolver", {
            "cwd": "babelrc",
            "alias": {
                "CompanyName.Products": "./path/to/typescript/build/output/CompanyName.Products/index.js"
            }
        }],
        ... other plugins ...
    ]
}

собака.ТС

import b = require('./baseTypes');

export module Living.Things {
    // Error, can't find name 'Animal', ??
    // Solved: can find, if properly referenced; exporting modules is useless, anyhow
    export class Dog extends b.Living.Things.Animal {
        public woof(): void {
            return;
        }
    }
}

дерево.ТС

// Error, can't use the same name twice, ??
// Solved: cannot declare let or const variable twice in same scope either: just use a different name
import b = require('./baseTypes');
import d = require('./dog');

module Living.Things {
    // Why do I have to write b.Living.Things.Plant instead of b.Plant??
    class Tree extends b.Living.Things.Plant {
    }
}

некромантии.
Если я правильно понимаю, вы спрашиваете, как иметь все ваши классы в одном отдельном файле, сохраняя при этом одно пространство имен для них всех.

поскольку ни у кого нет хорошего решения - вот идея простого решения, которое даже не включает typescript: это решение называется залпом.

Просто поместите все свои классы, которые должны быть в одном пространстве имен, в одну папку (полезно для организации кода в любом случае). Затем добавьте задачу gulp, которая объединяет все файлы в этом каталоге в один файл (gulp-concat). Затем добавьте пространство имен с тем же именем, что и верхний каталог, затем добавьте объединенные файлы, затем добавьте закрывающую скобку и сохраните в один файл.
Сделанный.
Затем добавьте gulp-задачу, которая следит за изменениями (и дополнениями/удалениями) в том же каталоге. При изменении / добавлении запустите функцию concat.

Теперь у вас есть все классы в один файл и один файл, содержащий все классы в одном пространстве имен.
Пример кода-по строкам:

gulp.task("js:min:all", function ()
{
    return gulp.src(["./wwwroot/app/**/*.js", "!" + "./wwwroot/app/**/*.min.js"
        , "./wwwroot/GeneratedScripts/**/*.js", "!" + "./wwwroot/GeneratedScripts/**/*.min.js"], { base: "." })
        .pipe(concat("./wwwroot/js/myscripts.min.js"))
        .pipe(uglify())
        .pipe(gulp.dest("."));
});



gulp.task('watch:js', function ()
{
    gulp.watch('js/**/*.js', ['js:min:all']);
});

здесь есть модуль gulp append-prepend: https://www.npmjs.com/package/gulp-append-prepend

var gap = require('gulp-append-prepend');

gulp.task('myawesometask', function(){
    gulp.src('index.html')
    .pipe(gap.prependFile('header.html'))
    .pipe(gap.prependText('<!-- HEADER -->'))
    .pipe(gap.appendText('<!-- FOOTER -->'))
    .pipe(gap.appendFile('footer.html'))
    .pipe(gulp.dest('www/'));
});

наконец, установите watcher для запуска загрузки решения, и все готово.