一.유래
네임스페이스는 JavaScript 중의 [모듈 패턴](/articles/모듈 패턴-javascript 디자인 패턴 2/) 에서 유래합니다:
var MyModule = {};
(function(exports) {
// 프라이빗 변수
var s = "hello";
// 공개 함수
function f() {
return s;
}
exports.f = f;
})(MyModule);
MyModule.f();
// 오류 MyModule.s is not a function
MyModule.s();
2 부분으로 구성됩니다:
- 모듈 클로저 (module closure): 모듈 실장을 캡슐화하고, 스코프를 격리
- 모듈 오브젝트 (module object): 해당 모듈이 노출하는 변수와 함수
후에 이 기초 위에서 모듈 동적 로드, 여러 파일로 분할 등의 서포트를 확장
TypeScript 는 모듈 패턴과 클래스 패턴을 결합하여 일종의 모듈 메커니즘을 실장했습니다. 즉 네임스페이스:
namespace MyModule {
var s = "hello";
export function f() {
return s;
}
}
MyModule.f();
// 오류 Property 's' does not exist on type 'typeof MyModule'.
MyModule.s;
컴파일 산물은 고전적인 모듈 패턴:
var MyModule;
(function (MyModule) {
var s = "hello";
function f() {
return s;
}
MyModule.f = f;
})(MyModule || (MyModule = {}));
MyModule.f();
MyModule.s;
二.작용
모듈과 유사하게, 네임스페이스도 일종의 코드 조직 방식:
namespace Page {
export interface IPage {
render(data: object): string;
}
export class IndexPage implements IPage {
render(data: object): string {
return '<div>Index page content is here.</div>';
}
}
}
// 컴파일 결과
var Page;
(function(Page) {
var IndexPage = /** @class */ (function() {
function IndexPage() {}
IndexPage.prototype.render = function(data) {
return '<div>Index page content is here.</div>';
};
return IndexPage;
})();
Page.IndexPage = IndexPage;
})(Page || (Page = {}));
마찬가지로 스코프 격리 (상예는 Page 1 개의 글로벌 변수만 노출) 를 가지며, 파일별로 모듈을 분할하는 것도 서포트:
// IPage.ts
namespace Page {
export interface IPage {
render(data: object): string;
}
}
// IndexPage.ts
/// <reference path="./IPage.ts" />
namespace Page {
export class IndexPage implements IPage {
render(data: object): string {
return '<div>Index page content is here.</div>';
}
}
}
// App.ts
/// <reference path="./IndexPage.ts" />
/// <reference path="./DetailPage.ts" />
let indexPageContent = new Page.IndexPage().render({value: 'index'})
컴파일 결과:
// IPage.js
空
// IndexPage.js
/// <reference path="./IPage.ts" />
var Page;
(function (Page) {
var IndexPage = /** @class */ (function () {
function IndexPage() {
}
IndexPage.prototype.render = function (data) {
return '<div>Index page content is here.</div>';
};
return IndexPage;
}());
Page.IndexPage = IndexPage;
})(Page || (Page = {}));
// App.js
/// <reference path="./IndexPage.ts" />
/// <reference path="./DetailPage.ts" />
var indexPageContent = new Page.IndexPage().render({ value: 'index' });
여기서 트리플 슬래시 지시문을 통해 분할된「namespace 모듈」을 도입 (module 의 like import 는 아님) 함에 주의. still import 를 사용하면, 오류가 보고됩니다:
// 오류 File '/path/to/IndexPage.ts' is not a module.ts(2306)
import IndexPage from "./IndexPage";
P.S. 另外, --outFile 옵션을 통해 1 개의 JS Bundle 을 생성 가능 (디폴트는 컴파일하여 대응하는 동명 JS 散 파일을 생성)
三.트리플 슬래시 지시문
6 종류의 지시문을 서포트:
- 파일 간 의존을 기술:
/// <reference path="./myFile.ts" />, 현재의 디렉토리 하의myFile.ts를 인용 - (타입) 선언 의존을 기술:
/// <reference types="node" />,@types/node/index.d.ts타입 선언을 인용,--types옵션에 대응 - 명시적으로 내장 (타입) 라이브러리 파일을 인용:
/// <reference lib="es2015" />또는/// <reference lib="es2017.string" />, 내장 (타입) 라이브러리lib.es2015.d.ts또는lib.es2017.string.d.ts를 인용,--lib컴파일 옵션에 대응 - 디폴트 라이브러리를 무효:
/// <reference no-default-lib="true"/>, 컴파일 프로세스 중에 디폴트 라이브러리를 로드하지 않음,--noLib컴파일 옵션에 대응, 동시에 현재의 파일을 디폴트 라이브러리로 마크 (이에 의해--skipDefaultLibCheck옵션이 해당 파일의 체크를 스킵 가능) - 현재의 모듈의 AMD 모듈 이름을 지정:
///<amd-module name="NamedModule"/>, AMD 모듈 이름을NamedModule로 지정 - AMD 모듈 의존을 지정 (폐기됨):
/// <amd-dependency path="legacy/moduleA" name="moduleA"/>,legacy/moduleA에 의존하고, 도입 모듈 이름을moduleA로 지정 (name속성은 옵션)
P.S. 더 많은 예는, Triple-Slash Directives 참조
형식상에는 3 개의 슬래시로 시작하므로, 트리플 슬래시 지시문 (triple-slash directives) 이라고 불립니다. 그 중에서 XML 태그는 컴파일 지시문을 표현합니다. 其它의 주의사항은 다음과 같습니다:
- 파일 수행 (주석을 제외) 에 출현하지 않으면 유효하지 않습니다. 즉, 1 개의 트리플 슬래시 지시문 앞에 단행 주석, 복수행 주석 또는 其它의 트리플 슬래시 지시문만 출현 가능
/// <amd-dependency />지시문은 폐기됨,import "moduleName";로 대체--noResolve옵션을 지정하면, 모든/// <reference path="..." />지시문을 무시 (이러한 모듈을 도입하지 않음)
작용상, /// <reference path="..." /> 는 CSS 중의 @import 와 유사 (--outFile 옵션을 지정할 때, 모듈 통합 순서는 path reference 지시문 순서와 일치)
실장상, 전처리 단계에서 깊이 우선으로 모든 트리플 슬래시 지시문을 해석하고, 지정된 파일을 컴파일 프로세스에 추가
P.S. 其它의 위치에 출현하는 트리플 슬래시 지시문은普通の 단행 주석으로 취급되며, 오류는 아니지만, 무효 (컴파일러는 인식하지 않음)
四.별명
네임스페이스는 네스트를 서포트하므로, 심층 네스트의 상황이 발생할 수 있습니다:
namespace Shapes {
export namespace Polygons {
export class Triangle { }
export class Square { }
}
}
此时 별명을 통해 모듈 인용을 간소화 가능:
import P = Shapes.Polygons;
import Triangle = Shapes.Polygons.Triangle;
let sq = new P.Square();
let triangle = new Triangle();
// 컴파일 후
var P = Shapes.Polygons;
var Triangle = Shapes.Polygons.Triangle;
var sq = new P.Square();
var triangle = new Triangle();
어렵지 않게 발견, 여기의 import 는 var 의 구문 사탕:
This is similar to using var, but also works on the type and namespace meanings of the imported symbol.
따라서값에 별명을 붙일 때 새로운 참조를 생성:
Importantly, for values, import is a distinct reference from the original symbol, so changes to an aliased var will not be reflected in the original variable.
예를 들어:
namespace NS {
export let x = 1;
}
import x = NS.x;
import y = NS.x;
(x as any) = 2;
y === 1; // true
// 컴파일 후
var NS;
(function (NS) {
NS.x = 1;
})(NS || (NS = {}));
var x = NS.x;
var y = NS.x;
x = 2;
y === 1; // true
P.S. import q = x.y.z 별명 구문은 네임스페이스에만 적용, 우측은 반드시 namespace 액세스여야 합니다
五.namespace 와 module
TypeScript 1.5 之前只有
module关键字,不区分内部模块(internal modules)与外部模块(external modules),二者都通过module关键字来定义。后来清晰起见,新增namespace关键字表示内部模块
(namespace keyword 에서 인용)
簡言之, 키워드 module 와 namespace 는 구문상에서 완전히 동등, 예를 들어:
namespace Shape.Rectangle {
export let a;
export let b;
export function getArea() { return a * b; }
}
와
module Shape.Rectangle {
export let a;
export let b;
export function getArea() { return a * b; }
}
의 컴파일 결과는 모두:
var Shape;
(function (Shape) {
var Rectangle;
(function (Rectangle) {
function getArea() { return Rectangle.a * Rectangle.b; }
Rectangle.getArea = getArea;
})(Rectangle = Shape.Rectangle || (Shape.Rectangle = {}));
})(Shape || (Shape = {}));
namespace 로 오래된 module 을 대체하는 것은 혼란을 피하기 위해 , 또는 ES Module, AMD, UMD 등의 Module 개념 (소위 외부 모듈) 에 길을 양보하기 위해. 왜냐하면 만약 module 키워드를 독점하고, 실제로 정의하고 있는 것이 Module 이 아니라 Namespace 라면, 매우 사람을 혼란시키는 일이기 때문
六.모듈과 네임스페이스
내부 모듈과 외부 모듈
즉:
- 내부 모듈: 즉 네임스페이스,
namespace또는module키워드로 선언 - 외부 모듈: 즉 모듈 (ES Module, CommonJS, AMD, UMD 등), 특별히 선언할 필요はなく, (
import또는export를 포함하는) 파일 즉 모듈
외부 모듈은 간단히외부 파일 중의 모듈로 이해 가능. 왜냐하면 동일 파일 중에서 복수의 다른 namespace 또는 module(즉 내부 모듈) 을 정의 가능하지만, 복수의 ES Module 을 정의할 수 없기 때문
P.S. 畢竟 네임스페이스는 실질적으로 IIFE 로, 모듈 로더와 관계なく, 파일 즉 모듈의 로드 메커니즘 제약이 존재하지 않음
개념 차이
개념상, TypeScript 는 ES Module 규범 (파일 즉 모듈) 을 따르고, 컴파일을 통해 CommonJS, AMD, UMD 등의 모듈 형식을 출력
而 네임스페이스는 JavaScript 중의 [모듈 패턴](/articles/모듈 패턴-javascript 디자인 패턴 2/) 에서 유래했으며, 구시대의 산물로 간주할 수 있습니다. 사용을 권장하지 않습니다([모듈 타입을 선언](/articles/모듈-typescript 노트 13/#articleHeader7) 하는 경우를 제외)
로드 메커니즘 차이
모듈 도입 메커니즘상, 네임스페이스는 트리플 슬래시 지시문을 통해 도입할 필요가 있으며, 소스 코드埋め込みに 상당 (CSS 중의 @import 와 유사), 현재의 스코프에 추가의 변수를 도입
P.S. 단일 파일 Bundle 에 팩하지 않는 경우, 런타임에서 이러한 트리플 슬래시 지시문을 통해 도입된 의존을 도입할 필요가 있습니다 (예를 들어 <script> 태그를 통해)
而 모듈은 import/require 등의 방식을 통해 도입되며, 호출측이 변수를 통해 그것을 인용하는지 여부를 결정하고, 현재의 스코프에 적극적으로 영향을 주지 않습니다
P.S. import "module-name"; 구문은 모듈 (의 부작용) 만 도입하고, 모듈을 인용하여 액세스하지 않음, 상세는 import 참조
最佳 실천
모듈과 네임스페이스의 사용상, 몇 가지 실천 경험이 있습니다:
- 네임스페이스 네스트 레벨을 감소, 예를 들어 정적 메서드만を持つ class 는 통상 불필요, 모듈 이름으로 충분히 시맨틱스를 표현
- 모듈이 1 개의 API 만 노출하는 경우, export default 의方が 더욱 적합, 도입이 더욱 편리,而且 호출측은 API 이름을気に할 필요가 없음
- 복수의 API 를 노출하는 경우, 모두 직접
export(수량이 너무 많은 경우, [모듈 오브젝트를 도입](/articles/module-es6 노트 13/#articleHeader5), 예를 들어import * as largeModule from 'SoLargeModule') - re-export 를 통해現有 모듈을 확장, 예를 들어
export as - 모듈 내에서 네임스페이스를 사용하지 않음, 왜냐하면 모듈本就에 논리 구조 (파일 디렉토리 구조) 와 모듈 스코프를 가지며, 네임스페이스는 더 많은 메리트를 제공할 수 없음
아직 댓글이 없습니다