1. ホーム
  2. typescript

[解決済み] モジュールと名前空間 - Import vs Require Typescript

2022-09-08 19:38:46

質問

私は module/namespace/exportimport, require, reference を使用することができます。 Java出身なので、いつ何を使えばいいのか、何が正しい設計なのか、どなたかわかりやすく説明していただけませんか?私はサンプルプロジェクトを書いているとき、私は台無しにしている感じです。

今のところ、私の理解は以下の通りです。 1. module は外部パッケージ用 2. namespace は内部パッケージ用

  • どのように分類するのかがわからなかったのですが?
  • クラスや名前空間、パッケージのエクスポートはいつ行うのか?
  • パッケージ/名前空間をエクスポートすると、その中のすべてのクラスがエクスポートされるか、または明示的にエクスポートする必要があります。
  • それぞれのクラスはどのようにインポート/要求されるのでしょうか?

ドキュメントによると 各マネージャー/モデルごとに "ts" ファイルを作成する場合、Typescript は "namespaces" の使用を推奨しないのでしょうか?直接参照パスを使用するのですか?

私は別の背景から来ており、ES6/ES5など`についてよくわからないので、詳細に説明してください。

私は何人かの人が同じ質問をし、混乱しているのを見たことがあります。私は誰かが実世界のシナリオで詳細に説明できることを望みます。

どのように解決するのですか?

どのように分類するのかがわからなかったのですが?

名前空間は、コードを整理/カプセル化するために使用されます。外部モジュールは、コードを整理/カプセル化するため、および実行時にコードの場所を特定するために使用されます。実際には、実行時に 2 つの選択肢があります。1)すべてのトランスパイルコードを 1 つのファイルにまとめる、または 2)外部モジュールを使用して複数のファイルを持ち、それらのファイルを取得するために何か他のメカニズムが必要です。

クラス、名前空間、またはパッケージをいつエクスポートするか?

型や値をそのファイルの外側から見えるようにするには、それが名前空間の中にある場合、それをエクスポートする必要があります。トップレベルでエクスポートするか、名前空間内でエクスポートするかによって、それが今、外部モジュールにあるかどうかが決まります。

パッケージ/名前空間をエクスポートすると、その中のすべてのクラスがエクスポートされるか、明示的にエクスポートする必要がある

名前空間内のクラスは、それが定義されているファイルの外側でコンパイル時に見えるようにするために、常に明示的にエクスポートされる必要があります。

それぞれがどのようにインポート/要求されることができますか?

これは、外部モジュールを使用しているかどうかによります。外部モジュールは、それを使用するために常にインポートされる必要があります。外部モジュールにない名前空間をインポートすることは、実際には名前空間のエイリアスを提供するだけで、型や何であれエイリアスのプレフィックスを付けなければなりません (これが一般的に外部モジュールで名前空間を使いたくない理由です。そうすると、外部モジュールによって提供される何かを参照するときに常にプレフィックスを使用しなければなりません。) 外部モジュールにない名前空間はファイルにまたがることができるので、同じ名前空間にいれば、ある種のインポートを必要とせずに、名前空間によってエクスポートされたものを参照することができます。

上記のことを本当に理解するためには、いくつかの背景知識が必要です。参照/名前空間/外部モジュールについて理解するための重要な点は、これらの構成がコンパイル時に何を行い、実行時に何を行うかということです。

参照ディレクティブは、コンパイル時に型情報を見つけるために使用されます。あなたのソースには特定のシンボルがあります。TypeScriptのコンパイラーはどのようにしてそのシンボルの定義を見つけるのでしょうか。tsconfig.jsonを使うことで、コンパイラにすべてのソースがどこにあるかを伝えることができます。

名前空間は型定義や実装を含むことができます。もし名前空間が型情報のみを含んでいるならば、それはランタイムマニフェステーションは全く持っていません。名前空間に実装コードがある場合、そのコードは名前空間と同じ名前のグローバル変数に割り当てられたクロージャの中にラップされます。ネストされた名前空間では、ルートの名前空間用のグローバル変数が存在することになります。もう一度、JSの出力を確認してください。名前空間は、歴史的にJSクライアントサイドライブラリが名前の衝突の問題を回避するために試みてきた方法です。そのアイデアは、ライブラリ全体をひとつのクロージャにまとめ、できるだけ小さなグローバルフットプリント、つまりクロージャを参照するひとつのグローバル変数だけを公開することです。しかし、問題は、グローバルな空間で名前を主張したことです。例えば、2つのバージョンのライブラリが必要な場合はどうだろうか。TypeScriptの名前空間には、名前空間のソースをどのように見つけるかという問題が残っている。つまり、A.Bを参照するソースコードには、コンパイラにA.Bの場所を教えるという問題がある -- 参照ディレクティブを使うか、tsconfig.jsonを使うか。あるいは、名前空間を外部モジュールに入れ、その外部モジュールをインポートすることによっても可能です。

外部モジュールの起源はサーバーサイドJSです。外部モジュールとファイルシステム上のファイルは一対一で対応しています。ファイルシステムのディレクトリ構造を使って、外部モジュールをネストした構造に整理することができます。 外部モジュールをインポートすると、一般的には常にその外部モジュールへの実行時依存性が発生します(例外は、外部モジュールをインポートしても、その値の位置でその外部モジュールを一切使用しない場合、つまり、その型情報を取得するためにのみ外部モジュールをインポートする場合です)。外部モジュールは暗黙のうちにクロージャの中にあり、これが鍵となる。モジュールのユーザーは、クロージャを好きなローカル変数に割り当てることができる。TypeScript/ES6では、外部モジュールのexportをローカル名にマッピングする構文が追加されているが、これは単なる小手先の処理に過ぎない。サーバー側では、外部モジュールを探すのは比較的簡単である。ローカルファイルシステム上にある外部モジュールを表すファイルを探すだけでよい。クライアントサイド、つまりブラウザで外部モジュールを使いたい場合、ファイルシステムにはロード可能なモジュールがないため、より複雑になってきます。そこで、クライアント側では、すべてのファイルをバンドルして、ブラウザでリモートから利用できるようにする方法が必要になります。ここで、Webpack(Webpackはモジュールをバンドルするだけではなく、もっとたくさんのことをします)やBrowserifyなどのモジュールバンドルが登場します。モジュール バンドルによって、ブラウザで外部モジュールを実行時に解決できるようになります。

実世界のシナリオ。AngularJS。外部モジュールは存在しないことにして、グローバル空間の汚染を制限するために単一の名前空間を使用し(以下の例では、単一の変数MyAppがグローバル空間にあるすべてです)、インターフェースのみをエクスポートし、実装を使用可能にするためにAngularJSの依存性注入を使用するようにします。すべてのクラスをディレクトリルートに置き、tsconfig.jsonをルートに追加し、angularjs typingsを同じディレクトリルートにインストールしてtsconfig.jsonがそれを拾うようにし、すべての出力を一つのJSファイルにまとめます。コードの再利用があまり気にならないのであれば、これはほとんどのプロジェクトでうまくいくでしょう。

MyService.tsです。

namespace MyApp {

    // without an export the interface is not visible outside of MyService.ts
    export interface MyService { 
        ....
    }

    // class is not exported; AngularJS DI will wire up the implementation
    class MyServiceImpl implements MyService {
    }

    angular.module("MyApp").service("myService", MyServiceImpl);
}

MyController.tsです。

namespace MyApp {

   class MyController {
       // No import of MyService is needed as we are spanning 
       // one namespace with multiple files.
       // MyService is only used at compile time for type checking. 
       // AngularJS DI is done on the name of the variable. 
       constructor(private myService: MyService) { 
       }
   }
   angular.module("MyApp").controller("myController", MyController);
}

IIFEを使用して、グローバルな実行時スコープを汚染しないようにする。この例では、グローバル変数は全く作成されていません。(tsconfig.jsonが想定されています)。

Foo.tsです。

namespace Foo {
    // without an export IFoo is not visible. No JS is generated here
    // as we are only defining a type.
    export interface IFoo {
        x: string;
    }
}

interface ITopLevel {
    z: string;
}

(function(){
    // export required above to make IFoo visible as we are not in the Foo namespace
    class Foo1 implements Foo.IFoo {
        x: string = "abc";
    }
    // do something with Foo1 like register it with a DI system
})();

Bar.tsです。

// alias import; no external module created
import IFoo = Foo.IFoo;

(function() {

    // Namespace Foo is always visible as it was defined at
    // top level (outside of any other namespace).
    class Bar1 implements Foo.IFoo {
        x: string;
    }

    // equivalent to above
    class Bar2 implements IFoo {
        x: string;
    }

    // IToplevel is visible here for the same reason namespace Foo is visible
    class MyToplevel implements ITopLevel {
        z: string;
    }

})();

IIFEを使用すると、最初の例でMyAppをグローバル変数として導入することを排除することができます。

MyService.tsを作成します。

interface MyService { 
    ....
}

(function() {

    class MyServiceImpl implements MyService {
    }

    angular.module("MyApp").service("myService", MyServiceImpl);
})();

MyController.tsです。

(function() { 

   class MyController { 
       constructor(private myService: MyService) { 
       }
   }

   angular.module("MyApp").controller("myController", MyController);
})();