본문 바로가기

네임스페이스 사용법

namespace로 전역 코드를 계층화하고 여러 파일과 별칭으로 확장하며 현대 프로젝트에서 ES 모듈을 우선할 이유를 판단합니다.

타입스크립트는 초기 버전부터 코드 조직화를 위해 네임스페이스(Namespaces)를 제공해 왔습니다.

이 기능은 전역 스코프 오염을 줄이고 관련 코드를 묶는 데 유용했습니다.

다만 ES 모듈이 표준이 된 현재, 대부분의 현대 타입스크립트 프로젝트에서 네임스페이스 사용은 권장되지 않습니다.

그럼에도 불구하고 네임스페이스의 개념과 사용법을 이해하는 것은 중요합니다.

기존의 레거시 코드베이스를 다루거나, ES 모듈이 도입되기 전의 타입스크립트 코드를 분석할 때 마주칠 수 있기 때문입니다.

또한, 네임스페이스는 타입스크립트 내부적으로 타입 정의 파일(d.ts)에서 전역 객체나 외부 라이브러리의 구조를 모델링하는 데 여전히 사용되기도 합니다.

다음 그림은 선언 파일, 모듈 내부, 전역 스크립트 중 어느 문맥에 쓰인 네임스페이스인지부터 구분합니다.


네임스페이스의 선언과 구성

Validators의 공개 클래스와 내부 함수, 같은 스코프의 다중 파일 병합, namespace 별칭, ambient 선언과 모듈 내부 범위를 구분한다.

TYPE / RUNTIME / CONTRACT

namespace의 문맥과 공개 범위를 먼저 구분한다

본문 Validators는 전역 스크립트의 실행 코드입니다. 구현이 있는 namespace와 선언만 있는 namespace, 모듈 안의 namespace는 역할과 범위가 다릅니다.

namespace의 문맥과 공개 범위를 먼저 구분한다이 그림은 원래 단일 파일 Validators의 실행 클래스와 함수 범위입니다. app은 공개 클래스를 사용하고 LettersOnlyValidator는 같은 블록의 내부 함수를 사용합니다. interface는 타입 선언이므로 별도 실행 노드로 그리지 않았습니다.스크립트 선언 범위Validators 블록app.ts공개 클래스 사용ZipCodeValidatorexport된 클래스LettersOnlyValidator · exportisLetter이 블록 내부 함수
namespace의 문맥과 공개 범위를 먼저 구분한다이 그림은 원래 단일 파일 Validators의 실행 클래스와 함수 범위입니다. app은 공개 클래스를 사용하고 LettersOnlyValidator는 같은 블록의 내부 함수를 사용합니다. interface는 타입 선언이므로 별도 실행 노드로 그리지 않았습니다.스크립트 선언 범위Validators 블록app.ts공개 클래스 사용ZipCodeValidatorexport된 클래스LettersOnlyValidator · exportisLetter이 블록 내부 함수

이 그림은 원래 단일 파일 Validators의 실행 클래스와 함수 범위입니다. app은 공개 클래스를 사용하고 LettersOnlyValidator는 같은 블록의 내부 함수를 사용합니다. interface는 타입 선언이므로 별도 실행 노드로 그리지 않았습니다.

이 영역은 import/export 없는 스크립트의 선언 범위를 설명합니다. Node의 CommonJS 파일에서 실행하는 별도 실습은 파일 실행 범위 안에 머물며 브라우저 일반 script의 전역 객체 등록을 뜻하지 않습니다.

실행 스크립트

Validators의 공개 멤버

ZipCodeValidator와 LettersOnlyValidator는 export되어 밖에서 사용합니다. "12345"와 "Hello" 검사 결과는 각각 true입니다.

블록 내부

isLetter는 밖에서 보이지 않는다

isLetter는 선언한 namespace 블록 내부에서만 보입니다. 같은 이름으로 병합된 다른 블록에서도 이 함수에 접근할 수 없습니다.

다중 파일

같은 선언 스코프에서 병합

분할한 Validators.ZipCode.ts와 Validators.LettersOnly.ts를 함께 컴파일합니다. 실행 시에도 구현이 사용 코드보다 먼저 준비되어야 합니다. 단일 파일 예제와 중복 구현을 함께 넣지 않습니다.

별칭

기존 이름에 짧은 이름을 붙인다

import Validator = MyUtility.DataValidation은 모듈을 가져오는 import가 아닙니다. 기존 namespace에 별칭을 붙여 PhoneNumberValidator를 사용합니다.

선언 / 모듈 · 보충

값 생성과 파일 범위를 구분

declare namespace는 기존 API를 타입으로 설명하며 값을 만들지 않습니다. 외부 모듈 안의 namespace는 그 모듈의 범위에 머뭅니다.

ESM으로 전환

공개 API와 실행 순서를 보존

전역 실행 코드를 옮길 때 루트 이름, 공개 멤버, 숨겨진 상태, 사용 파일과 초기화 순서를 확인합니다. 기존 전역 API의 타입 선언은 별도로 검토합니다.

namespace는 export하지 않은 내부 멤버를 숨기지만 전역 스크립트의 루트 이름까지 없애지는 않습니다. 새 파일 의존성은 import/export 경계로 표현할 수 있습니다.

네임스페이스는 namespace 키워드를 사용하여 선언합니다.

네임스페이스 내부에 선언된 변수, 함수, 클래스 등은 기본적으로 해당 네임스페이스의 스코프 내에 캡슐화됩니다.

Validators.ts
namespace Validators {
  export interface StringValidator { // 네임스페이스 외부에서 사용하려면 export 필요
    isValid(s: string): boolean;
  }

  export class ZipCodeValidator implements StringValidator {
    isValid(s: string): boolean {
      return s.length === 5 && /^\d+$/.test(s);
    }
  }

  // 내부적으로만 사용되는 함수 (export 하지 않음)
  function isLetter(char: string): boolean {
    return /[a-zA-Z]/.test(char);
  }

  export class LettersOnlyValidator implements StringValidator {
    isValid(s: string): boolean {
      for (let i = 0; i < s.length; i++) {
        if (!isLetter(s[i])) { // 네임스페이스 내부에서는 자유롭게 접근 가능
          return false;
        }
      }
      return true;
    }
  }
}

위 예시에서 Validators 네임스페이스는 StringValidator 인터페이스, ZipCodeValidator 클래스, LettersOnlyValidator 클래스를 포함합니다.

isLetter는 export되지 않았으므로 이를 선언한 블록 내부에서만 접근할 수 있습니다. 같은 이름으로 병합된 다른 namespace 블록에는 노출되지 않습니다.


네임스페이스 멤버 사용하기

네임스페이스 내부에 export된 멤버를 사용하려면, 네임스페이스 이름을 접두사로 붙여 접근해야 합니다.

app.ts
// 타입 검사를 위해 관련 파일을 컴파일에 포함하고,
// 실행할 때도 합친 출력 또는 올바른 <script> 순서로 구현을 먼저 로드해야 합니다.

let zipValidator = new Validators.ZipCodeValidator();
let lettersValidator = new Validators.LettersOnlyValidator();

let zipCode = "12345";
let text = "Hello";

console.log(`"${zipCode}" is valid zip code: ${zipValidator.isValid(zipCode)}`); // true
console.log(`"${text}" consists of letters only: ${lettersValidator.isValid(text)}`); // true

// console.log(Validators.isLetter("a")); // Error: 'isLetter' 속성이 'typeof Validators' 형식에 없습니다. (export 되지 않아서)

네임스페이스의 멤버에 접근하는 방식은 객체의 속성에 접근하는 것과 유사합니다.

Validators.ZipCodeValidator, Validators.LettersOnlyValidator와 같이 사용합니다.


다중 파일 네임스페이스

네임스페이스는 여러 파일에 걸쳐 확장될 수 있습니다.

같은 선언 스코프를 공유하는 스크립트 파일에서는 동일한 이름의 네임스페이스가 병합됩니다. 다음 분할 예제는 앞의 단일 파일 구현을 대체하며, 같은 클래스 선언을 함께 넣지는 않습니다.

Validators.ZipCode.ts
namespace Validators {
  export class ZipCodeValidator {
    isValid(s: string): boolean {
      return s.length === 5 && /^\d+$/.test(s);
    }
  }
}
Validators.LettersOnly.ts
namespace Validators {
  export class LettersOnlyValidator {
    isValid(s: string): boolean {
      return /^[A-Za-z]+$/.test(s);
    }
  }
}

이렇게 두 파일을 컴파일하면, 마치 하나의 파일에 모든 내용이 정의된 것처럼 Validators 네임스페이스를 사용할 수 있습니다.

tsc 명령어 사용 시 모든 관련 파일을 함께 컴파일해야 합니다. (예: tsc Validators.ZipCode.ts Validators.LettersOnly.ts app.ts --outFile output.js)

위 outFile 명령은 구형 프로젝트에서 파일을 합치는 방식을 읽기 위한 예시입니다. TypeScript 6.0에서는 outFile에 폐기 예정 진단이 나므로 새 프로젝트의 파일 의존성은 ES 모듈로 표현하는 편이 좋습니다. 기존 코드의 구조를 공부할 때는 먼저 같은 파일 안에서 실행과 공개 범위를 확인할 수 있습니다.

같은 구조라도 검증 조건이 달라질 수 있다

단일 파일의 LettersOnlyValidator는 글자를 하나씩 검사합니다. 빈 문자열이면 반복을 한 번도 실행하지 않고 true를 반환합니다. 분할 예제의 정규식은 + 때문에 글자가 하나 이상 있어야 하므로 빈 문자열에는 false입니다. "Hello"가 두 구현에서 모두 통과해도 모든 입력의 결과까지 같다는 뜻은 아닙니다. 구조를 옮길 때 빈 입력을 허용하는 정책을 먼저 결정해야 합니다.

다음 확인은 원래 단일 파일 Validators.ts 블록 하나를 legacy-demo.ts에 저장한 뒤 같은 파일의 아래에 추가합니다. 분할 예제의 클래스는 중복해서 붙이지 않습니다. package.json은 { "type": "commonjs" }인 독립 폴더를 전제로 합니다.

legacy-demo.ts의 아래에 추가
const zip = new Validators.ZipCodeValidator();
const letters = new Validators.LettersOnlyValidator();
console.log(zip.isValid('12345'));
console.log(zip.isValid('1234'));
console.log(letters.isValid('Hello'));
console.log(letters.isValid(''));
console.log(letters.isValid('A1'));
tsc legacy-demo.ts --strict --target ES2020 --module NodeNext --moduleResolution NodeNext --outDir dist
node dist/legacy-demo.js

출력은 true, false, true, true, false입니다. 선언·사용 코드가 같은 파일에 있어 실행 순서가 분명하고 outFile을 사용하지 않습니다. 이 Node 실습의 namespace는 CommonJS 파일 안에서 사용합니다. 브라우저의 일반 script 태그처럼 전역 객체에 루트 이름을 만들고 여러 파일이 이를 공유하는 구성과는 실행 범위가 다릅니다. 여러 파일의 전역 namespace를 자동으로 연결하는 설정을 대신한 것은 아닙니다.

정책과 공개 범위를 판단하기

입력 "AB", "", "A1"을 분할 예제의 정규식 구현에 전달하면 결과는 각각 무엇일까요? 같은 이름의 새 namespace 블록에서 원래 내부 함수 isLetter를 직접 호출할 수 있는지도 판단합니다. 빈 문자열을 허용해야 하는 정책이라면 정규식의 반복 조건에서 무엇을 바꿔야 할지도 적어보세요.

입력 정책과 블록 범위 확인

정규식 구현은 순서대로 true, false, false입니다. 원래 내부 함수는 선언한 블록에서만 보이므로 다른 namespace 블록에서 직접 호출할 수 없습니다. 빈 문자열을 허용하려면 반복 기호를 +에서 *로 바꿀 수 있지만, 이것은 공개 범위와 별개인 값 검증 정책 변경입니다. 원래 코드를 옮길 때는 이 조건을 확인한 뒤 의도적으로 선택합니다.

구조와 블록 공개 범위는 공식 namespace 설명, outFile 등의 전환 안내는 TypeScript 6.0 설명에서 대조할 수 있습니다.


네임스페이스 별칭

네임스페이스 이름이 길거나 여러 계층으로 중첩되어 있을 경우, import = 문법을 사용하여 별칭(alias)을 부여할 수 있습니다.

이는 코드 가독성을 높이는 데 도움이 됩니다.

namespace MyUtility.DataValidation {
  export class PhoneNumberValidator {
    isValid(phone: string): boolean {
      return phone.length > 7;
    }
  }
}

// 긴 네임스페이스에 별칭 부여
import Validator = MyUtility.DataValidation;

let phoneValidator = new Validator.PhoneNumberValidator();
console.log(`"123-456-7890" is a valid phone number: ${phoneValidator.isValid("123-456-7890")}`); // true

여기서 import Validator = MyUtility.DataValidation;은 ES 모듈을 로드하지 않고 기존의 한정된 이름에 별칭을 붙입니다. import =에는 CommonJS의 require와 결합하는 별도 형식도 있습니다.


네임스페이스 사용의 한계와 ES 모듈로의 전환

네임스페이스는 자바스크립트 파일들이 HTML script 태그를 통해 전역 스코프에 순서대로 로드되는 환경(특히 웹팩과 같은 번들러가 없던 시절)에서 유용했습니다.

그러나 다음과 같은 한계점 때문에 ES 모듈이 더 선호됩니다.

  • 컴파일 순서 및 번들링 문제: 여러 파일에 걸쳐 나뉜 네임스페이스를 사용하려면, 컴파일 시 파일들의 순서를 정확히 지정하거나 --outFile 옵션을 사용하여 하나의 파일로 번들링해야 하는 복잡성이 있었습니다. ES 모듈은 이러한 의존성 관리를 언어 차원에서 제공하므로 번들러가 훨씬 효율적으로 처리할 수 있습니다.
  • 전역 스코프 오염 가능성: export되지 않은 네임스페이스 내부 멤버는 캡슐화되지만, 이 예제처럼 전역 스크립트에 쓰인 네임스페이스의 루트 이름은 전역에 선언됩니다. 외부 모듈 안의 namespace는 모듈 내부에 머뭅니다. 이름 충돌 가능성이 완전히 사라지는 것은 아닙니다. ES 모듈은 파일 자체가 모듈 스코프를 형성하므로, 각 파일 내부의 최상위 레벨 선언도 기본적으로 전역 스코프를 오염시키지 않습니다.
  • 표준화 부재: 네임스페이스는 타입스크립트 고유의 기능이며 자바스크립트 표준에는 없습니다. 반면 ES 모듈은 자바스크립트의 공식 표준입니다. 표준을 따르는 것이 장기적인 유지보수와 생태계 호환성에 유리합니다.

권장 사항: 새로운 애플리케이션의 파일 의존성은 ES 모듈로 구성하는 편이 명확합니다. 기존 전역·UMD API를 설명하는 ambient namespace는 런타임 값을 생성하지 않는 선언 모델이므로, 실행 코드의 마이그레이션과 구분해 유지 여부를 판단합니다.

ES 모듈은 더 명확한 의존성 관리, 더 나은 트리 셰이킹(Tree Shaking) 지원(사용되지 않는 코드 제거), 그리고 자바스크립트 생태계 전반의 표준화라는 이점을 제공합니다.


네임스페이스는 타입스크립트가 처음 등장했을 때 코드 조직화를 돕는 중요한 기능이었지만, 이제는 ES 모듈 시스템이 그 역할을 대체했습니다.

비록 현대 개발에서는 거의 사용되지 않지만, 기존 코드를 이해하거나 특정 레거시 환경을 다룰 때 네임스페이스의 개념을 알고 있는 것이 여전히 유용할 수 있습니다.

레거시 코드를 현대 구조로 옮길 때는 네임스페이스의 전역 이름, export 멤버, 파일 로딩 순서를 ES 모듈의 import/export 경계로 대응시키면 됩니다.

네임스페이스를 유지할지 ES 모듈로 옮길지 결정할 때는 코드 실행 환경, 번들링 방식, 전역 이름 충돌 위험을 함께 따져야 합니다.


네임스페이스를 유지할지 ES 모듈로 옮길지 판단할 때는 이름 충돌, export 범위, 번들링 방식을 마지막으로 점검합니다.