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

157

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

baseTypes.ts

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

dog.ts

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

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

tree.ts

// 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. Невозможно объединить несколько объектов в одном и том же пространстве имен в файле. Как это сделать?

Теги:
module

8 ответов

578
Лучший ответ

Аналоговые игры

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

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

Mod1.ts

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

Mod2.ts

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

Mod3.ts

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

Вы создали эту настройку: Изображение 3469

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


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

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

global1.ts

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

global2.ts

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

global3.ts

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

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

Изображение 3470

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


Версия 3: Переход без чашечки

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

Mod1.ts

export class Twix { ... }

Mod2.ts

export class PeanutButterCup { ... }

Mod3.ts

export class KitKat { ... }

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

Изображение 3471

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

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


Это не те концепции, которые вы ищете

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

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

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

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

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

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


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

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

История:

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

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

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

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

"Но Боб..." вы отвечаете. "Ваша схема организации бесполезна. Вам нужно открыть кучу ящиков, чтобы добраться до чего-либо, и на самом деле нет ничего удобнее найти что-либо, кроме если бы вы просто поместили все в одну коробку вместо трех. Фактически, поскольку ваши кладовая уже сортируется по полкам, вам совсем не нужны коробки. Почему бы просто не поставить макароны на полку и не забрать ее, когда вам это нужно?

"Вы не понимаете - мне нужно убедиться, что никто не ставит что-то, что не принадлежит пространству имен" Pantry ". И я благополучно организовал все мои макароны в пространстве имен Pantry.Grains.Pasta поэтому я может легко найти его "

Боб очень смущен.

Модули - их собственная коробка

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

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


Руководство для внешних модулей

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

Экспортируйте как можно ближе к верхнему уровню

  • Если вы экспортируете только один класс или функцию, используйте export default:

MyClass.ts

export default class SomeType {
  constructor() { ... }
}

MyFunc.ts

function getThing() { return 'thing'; }
export default getThing;

потребление

import t from './MyClass';
import f from './MyFunc';
var x = new t();
console.log(f());

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

  • Если вы экспортируете несколько объектов, поместите их на верхнем уровне:

MyThings.ts

export class SomeType { ... }
export function someFunc() { ... }

потребление

import * as m from './MyThings';
var x = new m.SomeType();
var y = m.someFunc();
  • Если вы экспортируете большое количество вещей, только тогда вы должны использовать ключевое слово module/namespace:

MyLargeModule.ts

export namespace Animals {
  export class Dog { ... }
  export class Cat { ... }
}
export namespace Plants {
  export class Tree { ... }
}

потребление

import { Animals, Plants} from './MyLargeModule';
var x = new Animals.Dog();

Красные флаги

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

  • Файл, единственным объявлением верхнего уровня которого является export module Foo {... } (удалите Foo и переместите все вверх на уровень)
  • Файл, который имеет один export class export function или export function которая не export default
  • Несколько файлов, имеющих один и тот же export module Foo { на верхнем уровне (не думайте, что они собираются объединиться в один Foo !)
  • 60
    Это не ответ. Предположение, что вам не нужно или вам нужно пространство имен для внешних модулей, является ошибочным. Несмотря на то , что файловая система является своим родом схемы организации вы можете любопытное использовать для этих целей, это не так хорошо для потребителя , чтобы иметь операторы п импорта для использования п классов или функций из данного проекта; тем более, что это также портит соглашение об именах, когда вы не работаете в реальном коде.
  • 12
    Независимо от того, сколько человек может этого хотеть, это все равно невозможно .
Показать ещё 14 комментариев
39

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

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

Отредактируйте " Крутить", чтобы обновить этот ответ. Несколько подходов к namespacing появляются в 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();

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

  • 4
    Что такое "пространства имен ES6"?
  • 0
    @AluanHaddad при импорте es2015 +, импортируемые вещи либо по умолчанию, деструктурированные, либо с пространством имен. const fs = require('fs') , fs - пространство имен. import * as moment from 'moment' , moment - это пространство имен. Это онтология, а не спецификация.
Показать ещё 2 комментария
5

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

baseTypes.ts

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

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

dog.ts

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

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

tree.ts

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

class Tree extends b.Plant {
}

LivingThings.ts

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

export = {
    dog: dog,
    tree: tree
}

main.ts

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

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

  • 8
    LivingThings.dog.Dog это то, что у вас есть здесь.
  • 0
    Я рекомендую сохранять постоянство регистра букв, если вы экспортируете «Дерево», а затем импортируете «Дерево», а не «Дерево».
Показать ещё 1 комментарий
2

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

Если вы действительно хотите импортировать все из одного и того же имени модуля/псевдонима, объедините модуль-оболочку с отображением путей в 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 ...
    ]
}
2

OP Я с тобой человек. опять же, нет ничего плохого в этом ответе с более чем 300 голосами, но мое мнение таково:

  • Что не так, если поместить классы в свои уютные собственные собственные файлы? Я имею в виду, что это будет выглядеть намного лучше? (или кто-то, как 1000 строк для всех моделей)

  • тогда, если первая будет достигнута, нам нужно импортировать импорт импорта... импортировать только в каждый из файлов модели, например man, srsly, файл модели, файл .d.ts, почему там так много? это должно быть просто, аккуратно и так. Почему мне нужен импорт? Зачем? С# получили пространства имен по какой-либо причине.

  • И к тому времени вы буквально используете "filenames.ts" в качестве идентификаторов. Как идентификаторы... Приходите на его 2017 сейчас, и мы все еще это делаем? Има возвращается на Марс и спит еще 1000 лет.

К сожалению, мой ответ: nop, вы не можете использовать функциональность "пространства имен", если вы не используете все эти импорты или используете эти имена файлов в качестве идентификаторов (что, на мой взгляд, действительно глупо). Другой вариант: поместите все эти зависимости в поле с именем filenameasidentifier.ts и используйте

export namespace(or module) boxInBox {} .

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

2

Небольшое подчинение Альбинофанти:

base.ts

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

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

dog.ts

import * as b from './base';

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

things.ts

import { Dog } from './dog'

namespace things {
  export const dog = Dog;
}

export = things;

main.ts

import * as things from './things';

console.log(things.dog);
  • 2
    Спасибо за это! Просто хотел сказать, что изменения в существующем ответе желательно не публиковать в виде новых ответов: их следует либо добавить в качестве комментария к существующему ответу, либо (лучше) предложить, предложив изменить ответ, который вы хотите улучшить.
0

dog.ts

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;
        }
    }
}

tree.ts

// 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 {
    }
}
-3

Necromancing.

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

Так как никто, кажется, не имеет хорошего решения - вот идея простого решения, в которое даже не входит typescript: это решение называется Gulp.

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

Готово.

Затем добавьте gulp -task, которая следит за изменениями (и добавлениями/удалениями) в том же каталоге. При изменении/добавлении запускайте функцию 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/'));
});

Наконец, настройте наблюдателя на загрузку решения, и все готово.

  • 0
    Это сломало бы инкапсуляцию модуля среди других проблем

Ещё вопросы

Сообщество Overcoder
Наверх
Меню