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

233

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

baseTypes.ts

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

dog.ts

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

export namespace 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');

namespace 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. Это не работает, чтобы объединить несколько объектов в одном пространстве имен в файле. Как мне это сделать?

Райан Кавано
источник

Ответы:

861

Конфеты Кубок аналогия

Версия 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 { ... }
}

Вы создали эту настройку: введите описание изображения здесь

Каждый модуль (лист бумаги) получает свою собственную чашку с именем 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в глобальной области видимости:

введите описание изображения здесь

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


Версия 3: Идти без чашки

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

Mod1.ts

export class Twix { ... }

Mod2.ts

export class PeanutButterCup { ... }

Mod3.ts

export class KitKat { ... }

создать картинку, которая выглядит так:

введите описание изображения здесь

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

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


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

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

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

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

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

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

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


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

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

История:

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

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

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

"Здорово!" говорит Боб. «Все в пространстве имен!».

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

«Вы не понимаете - мне нужно убедиться, что никто не помещает что-то, что не принадлежит, в пространство имен« Кладовая ». И я благополучно организовал все свои макароны в 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 default
  • Несколько файлов, которые имеют одинаковые export module Foo {на верхнем уровне (не думайте, что они будут объединены в один Foo!)
Райан Кавано
источник
80
Это не ответ. Предположение о том, что вам не нужно или вам нужно пространство имен для внешних модулей, является ошибочным. Несмотря на то , что файловая система является своим родом схемы организации вы можете любопытное использовать для этих целей, это не так хорошо для потребителя , чтобы иметь п импорта заявления для использования п классов или функций из данного проекта; тем более, что это также портит соглашение об именах, когда вы не работаете в реальном коде.
Albinofrenchy
12
Независимо от того, сколько человек может этого хотеть, это все равно невозможно .
Райан Кавано
26
Я не понимаю, мы больше не пишем паскаль. С каких пор организуется использование файловой системы?
Дэвид
9
Вы можете иметь модуль «обертка», который импортирует и реэкспортирует все, что представляет интерес для потребителей вашей библиотеки. Но опять же, используя «пространство имен», вы не получите никакого значения, кроме принудительного установления другого уровня косвенности для любого, кто использует ваш код.
Райан Кавано
13
Отличная рецензия, спасибо. Я чувствую, что вы должны сделать ссылку на это на www.typescriptlang.org/docs/handbook/namespaces.html. Я, должно быть, прочитал эту ссылку typescriptlang.org 3 или 4 раза, и, как разработчик C #, я, естественно, хочу поместить все в пространство имен. Я читал некоторые предложения, в которых говорилось, что нет, но без объяснения причин и ничего более определенного (и хорошо описанного), чем это. Плюс ничто в документах машинописного текста не упоминает этот AFAIK
Адам Плохер
53

Ничего плохого в ответе Райана, но для тех, кто пришел сюда и ищет, как сохранить структуру « один класс на файл» при правильном использовании пространств имен 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();

Этот псевдоним абсолютно отвратителен, поэтому не делайте этого. Вам лучше с подходом выше. Лично я предпочитаю «бочку».

Jefftopia
источник
6
Что такое "пространства имен ES6"?
Алуан Хаддад
@AluanHaddad при импорте es2015 + импортируемые вещи либо по умолчанию, деструктурированные, либо с пространством имен. const fs = require('fs'), fsэто пространство имен. import * as moment from 'moment', momentэто пространство имен. Это онтология, а не спецификация.
Джеффтопия
Я знаю об этом, но вы бы хорошо объяснили это в своем ответе. Пространства имен ES6 на самом деле вещь, однако, и requireпример не применяется к ним по ряду причин, включая то, что пространства имен ES6 не могут быть вызваны, в то время как requireвозвращает простой объект, который вполне может быть вызван.
Алуан Хаддад
1
Я не слежу, потому что, является ли импортируемая вещь вызываемой или нет, она все еще служит пространством имен, логически говоря. Я не думаю, что предостережения являются существенными для моего ответа выше.
Джеффтопия
7

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

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 потребителю компактный, разумный способ, который не зависит от того, какой тип модульной системы вы используете для проекта.

Albinofrenchy
источник
8
LivingThings.dog.Dog - это то, что у вас есть здесь.
Кори Аликс
Я рекомендую сохранять постоянство регистра букв, если вы экспортируете «Дерево», а затем импортируете «Дерево», а не «Дерево».
demisx
1
Кроме того, как вы можете импортировать что-либо, tree.tsкогда у него вообще нет экспортируемого члена?
demisx
Man TS, конечно, имеет какой-то глупый старый синтаксис, как importи requireвместе в одном утверждении.
Энди
3

Небольшое наложение альбинофренчего ответа:

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
Спасибо за это! Просто хотел сказать, что изменения в существующем ответе предпочтительно не следует публиковать в виде новых ответов: их следует либо добавить в качестве комментария к существующему ответу, либо (лучше) предложить, предложив изменить ответ, который вы хотите улучшить.
a3nm
3

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

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

  2. Итак, если первое будет достигнуто, мы должны импортировать import import ... import только в каждый из файлов модели, таких как man, srsly, файл модели, файл .d.ts, почему их так много * там? все должно быть просто, аккуратно и все. Зачем мне там импорт? Зачем? C # получил пространства имен по причине.

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

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

export namespace(or module) boxInBox {} .

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

НЕТ ... Ошибки ...
источник
3

Некоторые из вопросов / комментариев, которые я видел по этой теме, звучат для меня так, как будто человек использует то, 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 ...
    ]
}
Райан Томас
источник
1

Попробуйте этот модуль пространств имен

namespaceModuleFile.ts

export namespace Bookname{
export class Snows{
    name:any;
    constructor(bookname){
        console.log(bookname);
    }
}
export class Adventure{
    name:any;
    constructor(bookname){
        console.log(bookname);
    }
}
}





export namespace TreeList{
export class MangoTree{
    name:any;
    constructor(treeName){
        console.log(treeName);
    }
}
export class GuvavaTree{
    name:any;
    constructor(treeName){
        console.log(treeName);
    }
}
}

bookTreeCombine.ts

--- часть компиляции ---

import {Bookname , TreeList} from './namespaceModule';
import b = require('./namespaceModule');
let BooknameLists = new Bookname.Adventure('Pirate treasure');
BooknameLists = new Bookname.Snows('ways to write a book'); 
const TreeLis = new TreeList.MangoTree('trees present in nature');
const TreeLists = new TreeList.GuvavaTree('trees are the celebraties');
Бал мукунд кумар
источник
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 {
    }
}
Алессандро Лендаро
источник
-1

Правильный способ организовать ваш код - использовать отдельные каталоги вместо пространств имен. Каждый класс будет в своем собственном файле, в соответствующей папке пространства имен. index.ts будет только реэкспортировать каждый файл; фактический код не должен быть в файле index.ts. Подобная организация вашего кода значительно упрощает навигацию и самодокументируется на основе структуры каталогов.

// index.ts
import * as greeter from './greeter';
import * as somethingElse from './somethingElse';

export {greeter, somethingElse};

// greeter/index.ts
export * from './greetings.js';
...

// greeter/greetings.ts
export const helloWorld = "Hello World";

Затем вы бы использовали его как таковой:

import { greeter } from 'your-package'; //Import it like normal, be it from an NPM module or from a directory.
// You can also use the following syntax, if you prefer:
import * as package from 'your-package';

console.log(greeter.helloWorld);
NolePTR
источник
Это вводит в заблуждение и абсолютно неверно. Это не то, как работают пространства имен. Также это не отвечает на вопрос ops.
AndrewMcLagan