Sqsung DevLog

<타입스크립트 프로그래밍> 10장: Namespace.Modules 정리 본문

TypeScript

<타입스크립트 프로그래밍> 10장: Namespace.Modules 정리

sqsung 2023. 6. 16. 15:31

10장: Namespaces.Modules

10-1. 가볍게 살펴보는 자바스크립트 모듈의 역사

  • 초반 JS는 모듈 시스템을 전혀 지원하지 않았다
    • 즉, 모든 것을 전역 네임스페이스에 정의했고, 이 때문에 프로그램을 만들고 확장하기 어려웠다
    • 그래서 개발자들은 즉시 실행 함수나 객체를 이용하여 모듈을 흉내냈다
  • JS를 로딩하고 실행되는 동안 브라우저의 UI는 블록되기 때문에 프로그램이 커지고 코드가 늘어날 수록 브라우저는 점점 느려졌다
    • JS 출시 10년 뒤부터서야 첫 페이지가 로딩된 다음 JS를 게으르게, 그리고 비동기적으로 로딩하는 모듈 로더를 제공했다
  • 비슷한 시기에 출시된 Node.js에서는 JS의 확장성 문제 및 기타 교훈을 통해 모듈 시스템을 플랫폼 자체에 추가하기로 결정되었고, CommonJS 모듈 표준을 통해 이를 해결했다

10-2. import, export

  • 꼭 필요한 상황이 아니라면 TS에서는 CommonJS, 전역, 네임스페이스로 구분한 모듈보다는 ES2015 import와 export를 사용하는 것이 좋다

  • JS와 다르게 타입과 인터페이스도 import/export할 수 있으며, 타입과 값은 별도의 네임스페이스에 존재하므로 하나의 이름으로 한 개는 값 수준, 다른 한 개는 타입 수준으로 import 할 수 있다

    // 1. firstFile.ts
    export let x = 3;
    export type x = { y: string };
    
    // 2. secondFile.ts
    import { x } from './firstFile';
    
    let a = x + 1; // x는 값 x를 가리킴
    let b: x = { y: 'James' }; // x는 타입 x를 가리킴

10-2-1. 동적 임포트

  • 응용 프로그램이 커지면 첫 렌더링 시간이 점점 길어진다. 프론트엔드에서는 코드를 분할하여 이러한 문점을 보완할 수 있다.

  • 큰 파일 하나에 모든 코드를 넣지 말고, JS 파일을 여러 개 생성하여 나누어 저장하는 방법이다

    • 코드를 분할하면 여러 조각을 병렬로 로딩할 수 있으므로 용량이 큰 네트워크 요청을 더 수월하게 처리할 수 있다
  • LABjs와 관련 라이브러리는 필요할 때만 코드를 로딩하는 게으른 로딩의 개념을 소개했으며, 이를 동적 임포트라는 개념으로 공식화했다

    let locale = await import('locale_us-en');
  • import에는 문자열이라면 무엇이든 전달할 수 있지만, 대신 타입 안전성을 잃게 된다

  • 동적 임포트를 안전하게 하기 위해서는 아래 방법 중 하나를 이용해야 한다

    💡 동적 임포트 안전하게 사용하는 방법
    
    1. 문자열을 변수에 할당하지 않고, import에 문자열 리터럴로 직접 제공한다
    
    2. import에 표현식을 전달하고 모듈의 시그니처를 직접 명시한다

10-2-3. 모듈 모드 vs. 스크립트 모드

  • TS는 TS파일이 import나 export를 포함하느냐를 기준으로 해당 파일을 모듈 모드, 스크립트 모드 중 하나로 파싱한다
    • import/export 포함하면 모듈 모드로 파싱된다

10-3. 네임스페이스

  • Java, C#, PHP처럼 타입스크립트는 코드를 캡슐화할 수 있는 namespace 키워드를 제공한다

  • 다만, 다른 언어에서와 다르게 타입스크립트에서는 네임스페이스를 코드 캡슐화 수단으로 권하지 않는다 (모둘과 네임스페이스 중 고민이라면 모듈을 선택하는 것이 더 좋다)

  • 아래 예제는 네임스페이스 활용 방법 예재다

    // Get.ts
    namespace Network {
      export function get<T>(url: string): Promise<T> { /* ... */ }
    }
    
    // App.ts
    namespace App {
      Network.get<GitRepo>('https://api.github.com/repos/Microsoft/typescript');
    }
    💡 예제 설명:
    
    1. 네임스페이스는 만드시 (Network 같은) 이름이 있어야 함
    2. 변수, 타입, 인터페이스, 다른 네임스페이스 export 가능함
    3. export 되지 않은 내용은 네임스페이스 밖에서 볼 수 없음
    4. 하나의 네임스페이스를 여러 파일로 쪼개 관리할 수 있으며, TS가 알아서 동명 네임스페이스를 재귀적으로 합쳐줌
  • 네임스페이스 계층이 너무 길어지는 경우 별칭alias를 지어줄 수 있다

    // 1. RecursiveNamespace.ts
    namespace A {
      export namespace B {
        export namespace C {
          export let d = 3;
        }
      }
    }
    
    // 2. App.ts
    import d = A.B.C.d;
    
    let e = d * 3; // --> 9

10-3-1. 충돌

  • 같은 이름을 익스포트하면 충돌이 생긴다

    // 1. HTTPS.ts
    namespace Network {
      export function request<T> (url: string): T { /* ... */ }
    }
    
    // 2. HTTPS2.ts
    namespace Network {
      export function request<T>(url:string): T { /* ... */ }
    } // Error!: 중복된 함수 구현
  • 함수 타입을 정제할 때 사용하는 오버로드된 앰비언트 함수 선언(overloaded ambient function declaration)에는 이름 충돌 금지 규칙이 적용되지 않음

    namespace Network {
      export function request<T>(url: string): T
    }
    
    namespace Network {
      export function request<T>(url: string, priority: number): T
    }
    
    namespace Network {
      export function request<T>(url: string, algo: 'SHA1' | 'SHA256'): T
    }

10-4. 선언 합치기

  • 타입스크립트가 제공하는 3가지 합치기 기능은 아래와 같다

    💡 TS의 합치기
    
    1. 값과 타입 합치기:
      - 같은 이름으로 값 혹은 타입을 가리킬 수 있다
    
    2. 여러 네임스페이스를 하나로 합치기
    
    3. 여러 인터페이스를 하나로 합치기