Як використовувати простори імен із зовнішніми модулями 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() { }
  }
}

дерево.ц

// 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. Це не працює для об'єднання декількох об'єктів в одному просторі імен через файл. Як це зробити?

Відповіді:


860

Аналогія чашки цукерки

Версія 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 різних ящиків, кожна з яких позначена "комора". Ви вибираєте скриньку навмання і відкриваєте її. Всередині - одна коробка з написом "Зерна". Ви відкриваєте вікно "Зерна" і знаходите одну коробку з написом "Макаронні вироби". Ви відкриваєте вікно "Макаронні вироби" і знаходите одну коробку з написом "Пенне". Ви відкриєте цю скриньку і знайдете, як ви очікуєте, пакетик макаронних виробів Пенне.

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

"Це чудово!" каже Боб. "Все в просторі імен!".

"Але Боб ...", ти відповідаєш. "Ваша організаційна схема марна. Вам потрібно відкрити купу коробок, щоб дістатися до чого-небудь, і насправді не зручніше щось знайти, ніж якби ви просто помістили все в одну скриньку замість трьох . Насправді, оскільки комора вже сортується по полиці, коробки вам взагалі не потрібні. Чому б просто не встановити макарони на полицю і забрати її, коли вона вам потрібна? "

"Ви не розумієте - мені потрібно переконатися, що ніхто більше не вкладає те, що не належить до простору імен" Комора ". І я безпечно організував усі свої макарони в 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
Це невідповідь. Припущення, що вам не потрібно і не потрібно простору імен для зовнішніх модулів, є несправним. Хоча файлова система є своєрідною організаційною схемою, яку ви можете якось використовувати для цих цілей, споживачеві не так вже й приємно мати n заяв про імпорт для використання n класів або функцій певного проекту; тим більше, що це також затуманює умову іменування, коли ви перебуваєте в фактичному коді.
Альбінофренчі

12
Скільки б хто не хотів цього, все одно це неможливо .
Райан Кавано

26
Я не розумію, ми вже не пишемо паскаль. З того часу, коли організовується використання файлової системи шлях?
Девід

9
Ви можете, маючи модуль "обгортки", який імпортує та реекспортує все, що цікавить споживачів вашої бібліотеки. Але знову ж таки, використовуючи "простір імен", не буде надано жодного значення, окрім примусу іншого рівня непрямості для тих, хто використовує ваш код.
Райан Кавано

13
Чудовий запис, дякую. Я відчуваю, що вам слід зв’язатись із цим із www.typescriptlang.org/docs/handbook/namespaces.html. Я, мабуть, читав, що typecriptlang.org посилається 3 або 4 рази, і як C # Dev, я, природно, хочу все розмістити в просторі імен. Я прочитав кілька пропозицій, які говорять про те, щоб не робити, але не маючи пояснень, чому і нічого такого остаточного (і добре описаного), як це. Плюс до цього нічого в документах машинопису не згадує про це AFAIK
Адам Плочер

53

Немає нічого поганого у відповіді Райана, але для людей, які приїхали сюди, шукаючи, як підтримувати структуру одного класу на файл, в той час як правильно використовувати простори імен ES6, зверніться до цього корисного ресурсу від Microsoft.

Одне, що мені незрозуміло після прочитання документа, - це імпорт всього (злитого) модуля з одним import .

Змініть Поворот назад, щоб оновити цю відповідь. У ТС з’являється декілька підходів до простору імен.

Усі класи модулів в одному файлі.

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();

Цей псевдонім абсолютно огидний, тому не робіть цього. Вам краще з підходом вище. Особисто я віддаю перевагу «бочці».


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() { }
}   

дерево.ц

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 - це те, що у вас тут.
Корі Алікс

Я рекомендую зберігати регістр букв послідовно, якщо ви експортуєте "Дерево", то імпортуйте "Дерево", а не "дерево".
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

ОП Я з тобою чоловік. Знову ж таки, немає нічого поганого в цій відповіді з 300 і більше голосами, але моя думка така:

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

  2. тож, якщо буде досягнуто першого, нам доведеться імпортувати імпорт імпорту ... імпортувати лише у кожен модельний файл, наприклад man, srsly, файл моделі, файл .d.ts, чому так багато * s там? це має бути просто, охайно, і все. Навіщо мені потрібен імпорт? чому? C # отримав простори імен чомусь.

  3. І до того часу ви буквально використовуєте "filenames.ts" як ідентифікатори. Як ідентифікатори ... Приходьте на 2017 рік зараз, і ми все ще робимо це? Іма повертається на Марс і спить ще 1000 років.

На жаль, моя відповідь така: nop, ви не можете зробити функцію "простору імен" функціональною, якщо ви не використовуєте весь цей імпорт або використовуєте ці назви файлів як ідентифікатори (що, на мою думку, справді нерозумно). Ще один варіант: помістити всі ці залежності у вікно, яке називається filenameasidentifier.ts, і використовувати

export namespace(or module) boxInBox {} .

оберніть їх, щоб вони не намагалися отримати доступ до інших класів з такою ж назвою, коли вони просто намагаються отримати довідник від класу, який сидить прямо над ними.


3

Деякі питання / коментарі, які я бачив навколо цієї теми, звучать для мене так, ніби людина використовує 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 ...
    ]
}

1

Спробуйте цей модуль просторів імен

простір іменModuleFile.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;
        }
    }
}

дерево.ц

// 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);

Це вводить в оману і абсолютно некоректно. Це не так, як працюють простори імен. Крім того, він не відповідає на питання ops.
AndrewMcLagan
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.