Bagaimana cara menggunakan ruang nama dengan modul eksternal TypeScript?

233

Saya punya beberapa kode:

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 {

  }
}

Ini semua sangat membingungkan. Saya ingin memiliki banyak modul eksternal yang semuanya berkontribusi tipe ke namespace yang sama Living.Things,. Tampaknya ini tidak bekerja sama sekali - aku tidak bisa melihat Animaldi dogs.ts. Saya harus menulis nama namespace lengkap b.Living.Things.Plantdi tree.ts. Tidak berfungsi untuk menggabungkan beberapa objek di namespace yang sama di seluruh file. Bagaimana saya melakukan ini?

Ryan Cavanaugh
sumber

Jawaban:

860

Analogi Piala Permen

Versi 1: Gelas untuk setiap permen

Katakanlah Anda menulis beberapa kode seperti ini:

Mod1.ts

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

Mod2.ts

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

Mod3.ts

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

Anda telah membuat pengaturan ini: masukkan deskripsi gambar di sini

Setiap modul (lembaran kertas) diberi nama cangkirnya sendiriA . Ini tidak berguna - Anda tidak benar - benar mengatur permen Anda di sini, Anda hanya menambahkan langkah tambahan (mengeluarkannya dari cangkir) antara Anda dan camilan.


Versi 2: Satu cangkir dalam lingkup global

Jika Anda tidak menggunakan modul, Anda dapat menulis kode seperti ini (perhatikan kurangnya exportdeklarasi):

global1.ts

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

global2.ts

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

global3.ts

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

Kode ini menciptakan ruang nama gabungan Adi lingkup global:

masukkan deskripsi gambar di sini

Pengaturan ini berguna, tetapi tidak berlaku untuk modul (karena modul tidak mencemari lingkup global).


Versi 3: Tanpa cangkir

Kembali ke contoh asli, cangkir A, Adan Atidak melakukan apapun nikmat. Sebagai gantinya, Anda dapat menulis kode sebagai:

Mod1.ts

export class Twix { ... }

Mod2.ts

export class PeanutButterCup { ... }

Mod3.ts

export class KitKat { ... }

untuk membuat gambar yang terlihat seperti ini:

masukkan deskripsi gambar di sini

Jauh lebih baik!

Sekarang, jika Anda masih berpikir tentang seberapa besar Anda benar-benar ingin menggunakan namespace dengan modul Anda, baca terus ...


Ini Bukan Konsep yang Anda Cari

Kita perlu kembali ke asal-usul mengapa ruang nama ada di tempat pertama dan memeriksa apakah alasan itu masuk akal untuk modul eksternal.

Organisasi : Ruang nama berguna untuk mengelompokkan objek dan tipe yang berhubungan secara logis. Misalnya, dalam C #, Anda akan menemukan semua jenis koleksi di System.Collections. Dengan mengatur jenis kami ke dalam ruang nama hierarkis, kami memberikan pengalaman "penemuan" yang bagus untuk pengguna jenis itu.

Konflik Nama : Ruang nama penting untuk menghindari penamaan tabrakan. Misalnya, Anda mungkin memiliki My.Application.Customer.AddFormdan My.Application.Order.AddForm- dua jenis dengan nama yang sama, tetapi ruang nama yang berbeda. Dalam bahasa di mana semua pengidentifikasi ada dalam ruang lingkup root yang sama dan semua majelis memuat semua jenis, sangat penting untuk memiliki semuanya di namespace.

Apakah alasan itu masuk akal dalam modul eksternal?

Organisasi : Modul eksternal sudah ada dalam sistem file, tentu saja. Kita harus menyelesaikannya dengan path dan nama file, jadi ada skema organisasi logis untuk kita gunakan. Kita dapat memiliki /collections/generic/folder dengan listmodul di dalamnya.

Konflik Nama : Ini tidak berlaku sama sekali dalam modul eksternal. Dalam sebuah modul, tidak ada alasan yang masuk akal untuk memiliki dua objek dengan nama yang sama. Dari sisi konsumsi, konsumen dari setiap modul yang diberikan dapat memilih nama yang akan mereka gunakan untuk merujuk pada modul, sehingga konflik penamaan yang tidak disengaja tidak mungkin.


Bahkan jika Anda tidak percaya bahwa alasan-alasan itu cukup ditangani oleh bagaimana modul bekerja, "solusi" untuk mencoba menggunakan ruang nama dalam modul eksternal bahkan tidak berfungsi.

Kotak dalam Kotak dalam Kotak

Cerita:

Temanmu Bob memanggilmu. "Saya memiliki skema organisasi baru yang hebat di rumah saya", katanya, "lihatlah!". Rapi, mari kita lihat apa yang muncul Bob.

Anda mulai di dapur dan membuka dapur. Ada 60 kotak berbeda, masing-masing berlabel "Pantry". Anda memilih kotak secara acak dan membukanya. Di dalamnya ada satu kotak berlabel "Grains". Anda membuka kotak "Grains" dan menemukan satu kotak berlabel "Pasta". Anda membuka kotak "Pasta" dan menemukan satu kotak berlabel "Penne". Anda membuka kotak ini dan menemukan, seperti yang Anda harapkan, sekantong pasta penne.

Sedikit bingung, Anda mengambil kotak yang berdekatan, juga berlabel "Pantry". Di dalamnya ada satu kotak, sekali lagi berlabel "Grains". Anda membuka kotak "Grains" dan, sekali lagi, temukan satu kotak berlabel "Pasta". Anda membuka kotak "Pasta" dan menemukan satu kotak, yang ini diberi label "Rigatoni". Anda membuka kotak ini dan menemukan ... sekantong pasta rigatoni.

"Itu bagus!" kata Bob. "Semuanya ada dalam ruang nama!".

"Tapi Bob ..." jawabmu. "Skema organisasi Anda tidak berguna. Anda harus membuka banyak kotak untuk mencapai apa pun, dan sebenarnya tidak ada yang lebih nyaman untuk menemukan apa pun daripada jika Anda hanya meletakkan semuanya dalam satu kotak daripada tiga . Bahkan, karena Anda pantry sudah diurutkan dari rak ke rak, Anda tidak perlu kotak sama sekali. Mengapa tidak hanya meletakkan pasta di rak dan mengambilnya saat Anda membutuhkannya? "

"Kamu tidak mengerti - aku harus memastikan bahwa tidak ada orang lain yang memasukkan sesuatu yang tidak termasuk dalam ruang nama 'Pantry'. Dan aku telah dengan aman mengatur semua pastaku ke dalam Pantry.Grains.Pastaruang nama sehingga aku dapat dengan mudah menemukannya"

Bob adalah pria yang sangat bingung.

Modul adalah Kotaknya Sendiri

Anda mungkin pernah mengalami hal serupa terjadi dalam kehidupan nyata: Anda memesan beberapa hal di Amazon, dan setiap item muncul di kotaknya sendiri, dengan kotak yang lebih kecil di dalamnya, dengan barang Anda dibungkus dalam kemasannya sendiri. Bahkan jika kotak interiornya serupa, pengirimannya tidak bermanfaat "digabungkan".

Sesuai dengan analogi kotak, pengamatan utama adalah bahwa modul eksternal adalah kotak mereka sendiri . Ini mungkin item yang sangat kompleks dengan banyak fungsi, tetapi modul eksternal yang diberikan adalah kotaknya sendiri.


Panduan untuk Modul Eksternal

Sekarang kita sudah tahu bahwa kita tidak perlu menggunakan 'ruang nama', bagaimana kita mengatur modul kita? Beberapa prinsip dan contoh panduan mengikuti.

Ekspor sedekat mungkin ke tingkat atas

  • Jika Anda hanya mengekspor satu kelas atau fungsi, gunakan export default:

MyClass.ts

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

MyFunc.ts

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

Konsumsi

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

Ini optimal untuk konsumen. Mereka dapat memberi nama tipe Anda apa pun yang mereka inginkan ( tdalam hal ini) dan tidak perlu melakukan titik-titik asing untuk menemukan objek Anda.

  • Jika Anda mengekspor banyak objek, letakkan semuanya di tingkat atas:

MyThings.ts

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

Konsumsi

import * as m from './MyThings';
var x = new m.SomeType();
var y = m.someFunc();
  • Jika Anda mengekspor sejumlah besar hal, hanya maka Anda harus menggunakan module/ namespacekata kunci:

MyLargeModule.ts

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

Konsumsi

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

Bendera merah

Semua yang berikut ini adalah tanda merah untuk penataan modul. Periksa ulang apakah Anda tidak mencoba untuk namespace modul eksternal Anda jika salah satu dari ini berlaku untuk file Anda:

  • File yang hanya memiliki deklarasi tingkat atas export module Foo { ... }(hapus Foodan pindahkan semuanya ke tingkat atas)
  • File yang memiliki satu export classatau export functiontidakexport default
  • Banyak file yang memiliki tingkat yang sama export module Foo {di atas (jangan berpikir bahwa ini akan digabungkan menjadi satu Foo!)
Ryan Cavanaugh
sumber
80
Ini bukan jawaban. Premis yang Anda tidak perlu atau ingin ruang nama untuk modul eksternal adalah salah. Sementara sistem file adalah semacam skema organisasi Anda dapat agak gunakan untuk tujuan ini, tidak hampir sama baik bagi konsumen untuk memiliki n pernyataan impor untuk menggunakan kelas n atau fungsi dari proyek tertentu; terutama karena itu juga merusak konvensi penamaan ketika Anda turun dalam kode aktual.
Albinofrenchy
12
Tidak peduli berapa banyak yang orang inginkan, itu tetap tidak mungkin .
Ryan Cavanaugh
26
Saya tidak mengerti, kami tidak menulis pascal lagi. Sejak kapan mengatur menggunakan sistem file cara untuk pergi?
David
9
Anda dapat dengan memiliki modul "pembungkus" yang mengimpor dan mengekspor kembali semua yang menarik kepada konsumen perpustakaan Anda. Tetapi sekali lagi, menggunakan "namespace" tidak akan memberikan nilai apa pun selain memaksa tingkat tipuan lain bagi siapa pun yang menggunakan kode Anda.
Ryan Cavanaugh
13
Tulisan yang bagus, terima kasih. Saya merasa Anda harus menautkan ini dari www.typescriptlang.org/docs/handbook/namespaces.html. Saya pasti sudah membaca tautan typescriptlang.org 3 atau 4 kali dan sebagai seorang C # dev, tentu saja saya ingin meletakkan semuanya di namespace. Saya sudah membaca beberapa saran yang mengatakan tidak, tetapi tanpa penjelasan mengapa dan tidak ada yang definitif (dan dijelaskan dengan baik) seperti ini. Plus tidak ada dalam dokumen naskah yang menyebutkan AFAIK ini
Adam Plocher
53

Tidak ada yang salah dengan jawaban Ryan, tetapi bagi orang-orang yang datang ke sini mencari cara mempertahankan struktur satu kelas per file saat masih menggunakan ruang nama ES6 dengan benar, silakan merujuk ke sumber yang membantu ini dari Microsoft.

Satu hal yang tidak jelas bagi saya setelah membaca dokumen adalah: cara mengimpor seluruh modul (digabung) dengan satu import .

Edit Circling kembali untuk memperbarui jawaban ini. Beberapa pendekatan untuk namespacing muncul di TS.

Semua kelas modul dalam satu file.

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

Impor file ke dalam namespace, dan tetapkan kembali

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

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

Barel

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

Pertimbangan terakhir. Anda bisa namespace setiap file

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

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

Tetapi ketika seseorang mengimpor dua kelas dari namespace yang sama, TS akan mengeluh ada pengidentifikasi duplikat. Satu-satunya solusi saat ini adalah menggunakan alias namespace.

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

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

Alias ​​ini benar-benar menjijikkan, jadi jangan lakukan itu. Anda lebih baik dengan pendekatan di atas. Secara pribadi, saya lebih suka 'tong'.

Jefftopia
sumber
6
Apa itu "ES6 namespaces"?
Aluan Haddad
@AluanHaddad saat mengimpor es2015 +, hal-hal yang diimpor adalah default, destructured, atau namespaced. const fs = require('fs'), fsadalah namespace. import * as moment from 'moment', momentadalah namespace. Ini ontologi, bukan spesifikasinya.
Jefftopia
Saya sadar akan hal itu tetapi Anda sebaiknya menjelaskannya dalam jawaban Anda. Namun, ruang nama ES6 sebenarnya adalah sesuatu, dan requirecontoh ini tidak berlaku untuk mereka karena sejumlah alasan, termasuk bahwa ruang nama ES6 mungkin tidak dipanggil, sementara requiremengembalikan objek polos yang mungkin dapat dipanggil.
Aluan Haddad
1
Saya tidak mengikuti, karena apakah hal yang diimpor itu dapat dipanggil atau tidak, masih berfungsi sebagai namespace yang secara logis berbicara. Saya tidak berpikir peringatan adalah bahan untuk jawaban saya di atas.
Jefftopia
7

Cobalah mengatur berdasarkan folder:

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)

Idenya adalah bahwa modul Anda sendiri seharusnya tidak peduli / tahu mereka berpartisipasi dalam namespace, tetapi ini memaparkan API Anda kepada konsumen dengan cara yang ringkas dan masuk akal yang agnostik terhadap jenis sistem modul yang Anda gunakan untuk proyek tersebut.

Albinofrenchy
sumber
8
LivingThings.dog.Dog adalah apa yang Anda miliki di sini.
Corey Alix
Saya merekomendasikan agar huruf tetap konsisten, jika Anda mengekspor "Pohon", maka impor "Pohon", bukan "pohon".
demisx
1
Juga, bagaimana Anda bisa mengimpor sesuatu dari tree.tsketika tidak memiliki anggota yang diekspor sama sekali?
demisx
Man TS yakin memiliki sintaks tua yang konyol, suka importdan requirebersama dalam satu pernyataan.
Andy
3

Peningkatan kecil dari jawaban Albinofrenchy:

base.ts

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

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

dog.ts

import * as b from './base';

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

hal

import { Dog } from './dog'

namespace things {
  export const dog = Dog;
}

export = things;

main.ts

import * as things from './things';

console.log(things.dog);
Mike Vitik
sumber
2
Terima kasih untuk ini! Hanya ingin mengatakan bahwa perubahan pada jawaban yang ada sebaiknya tidak diposting sebagai jawaban baru: mereka harus ditambahkan sebagai komentar pada jawaban yang ada, atau (lebih baik) disarankan dengan menyarankan edit pada jawaban yang ingin Anda sampaikan. memperbaiki.
a3nm
3

OP Aku bersamamu kawan. lagi juga, tidak ada yang salah dengan jawaban itu dengan 300 suara lebih, tetapi pendapat saya adalah:

  1. apa yang salah dengan menempatkan kelas ke dalam file mereka sendiri yang hangat dan nyaman secara individual? Maksud saya ini akan membuat segalanya terlihat jauh lebih baik bukan? (atau seseorang seperti file 1000 baris untuk semua model)

  2. jadi, jika yang pertama akan tercapai, kita harus mengimpor impor impor ... impor hanya di masing-masing file model seperti man, srsly, file model, file .d.ts, mengapa ada begitu banyak * ada di sana? itu harus sederhana, rapi, dan hanya itu. Mengapa saya perlu impor di sana? Mengapa? C # mendapat ruang nama karena suatu alasan.

  3. Dan saat itu, Anda benar-benar menggunakan "filenames.ts" sebagai pengidentifikasi. Sebagai pengidentifikasi ... Ayo tahun 2017 sekarang dan kami masih melakukannya? Ima kembali ke Mars dan tidur selama 1000 tahun lagi.

Sedihnya, jawaban saya adalah: tidak, Anda tidak dapat membuat fungsi "namespace" berfungsi jika Anda tidak menggunakan semua impor itu atau menggunakan nama file tersebut sebagai pengidentifikasi (yang menurut saya sangat konyol). Opsi lain adalah: masukkan semua dependensi itu ke dalam kotak bernama filenameasidentifier.ts dan gunakan

export namespace(or module) boxInBox {} .

bungkus mereka sehingga mereka tidak akan mencoba mengakses kelas lain dengan nama yang sama ketika mereka hanya mencoba untuk mendapatkan referensi dari kelas yang berada tepat di atas mereka.

TIDAK ... Bug ...
sumber
3

Beberapa pertanyaan / komentar yang saya lihat di sekitar subjek ini terdengar bagi saya seolah-olah orang itu menggunakan apa yang Namespacemereka maksud dengan 'modul alias'. Seperti yang disebutkan Ryan Cavanaugh dalam salah satu komentarnya, Anda dapat memiliki modul 'Wrapper' yang mengekspor kembali beberapa modul.

Jika Anda benar-benar ingin mengimpor semuanya dari nama / alias modul yang sama, gabungkan modul pembungkus dengan pemetaan jalur di tsconfig.json.

Contoh:

./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'

Catatan : Resolusi modul dalam file .js keluaran perlu ditangani entah bagaimana, seperti dengan https://github.com/tleunen/babel-plugin-module-resolver ini

Contoh .babelrcuntuk menangani resolusi alias:

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

Coba modul ruang nama ini

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

--- kompilasi bagian ---

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');
Bal mukund kumar
sumber
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 {
    }
}
Alessandro Lendaro
sumber
-1

Cara yang tepat untuk mengatur kode Anda adalah dengan menggunakan direktori terpisah sebagai ganti ruang nama. Setiap kelas akan berada di file itu sendiri, di folder namespace masing-masing. index.ts hanya akan mengekspor kembali setiap file; tidak ada kode aktual yang harus ada dalam file index.ts. Pengorganisasian kode Anda seperti ini memudahkan navigasi, dan mendokumentasikan diri sendiri berdasarkan pada struktur direktori.

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

Anda kemudian akan menggunakannya seperti itu:

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
sumber
Ini menyesatkan dan sama sekali tidak benar. Itu bukan cara ruang nama bekerja. Juga tidak menjawab pertanyaan ops.
AndrewMcLagan