👶 TypeScript

프로토타입 안전하게 확장하기

개발자 린다씨 2023. 1. 22. 16:00
반응형

프로토타입 안전하게 확장하기

예전엔 프로토타입 확장이 안전하지 않은 일이었지만 이제 TypeScript처럼 정적 타입 시스템을 이용하면 안전하게 확장할 수 있습니다.

 

예시를 위해 Array 프로토타입에 zip 메서드를 추가해 보겠습니다.

 

프로토타입을 안전하게 확장하기 위해 두 단계로 진행할 것입니다.

 

먼저 .ts 파일에서 Array의 프로토타입을 확장한 다음 새로운 zip 메서드를 프로토타입에 추가합니다.

// TS에 zip이 무엇인지 설명
interface Array<T>{ // ①
  zip<U>(list: U[]): [T, U][]
}

// .zip 구현
Array.prototype.zip = function<T, U> (
  this: T[], // ②
  list: U[]
): [T, U][] {
  return this.map((v, k)=>
    tuple(v, list[k]) // ③
  )
}

1. 우선 TypeScript에게 zip을 Array에 추가하도록 지시했습니다. 인터페이스 합치기 기능을 이용해 전역 범위로 정의된 Array<T> 인터페이스에 zip 메서드를 추가했습니다.

 

파일에서 임포트(import)나 익스폴트(export)를 명시하지 않았으므로 기존의 전역 인터페이스와 같은 이름인 Array<T> 인터페이스를 직접 선언할 수 있었고 TypeScript는 자동으로 둘을 합쳐줍니다.

 

파일이 모듈 모드라면 전역 확장을 declare global이라는 타입 선언으로 감싸야합니다.

declare global{
  interface Array<T>{ // ①
    zip<U>(list: U[]): [T, U][]
  }
}

global은 전역으로 정의된 모든 값을 포함하는 특별한 네임스페이스로, 이를 이용하면 모듈 모드의 파일에서 전역 범위에 존재하는 이름들도 확장할 수 있게 됩니다.

 

2. 그리고 Array의 프로토타입에 zip 메서드를 구현했습니다. 

this 타입을 사용하여 TypeScript가 .zip이 호출되는 대상 배열에서 T 타입을 올바로 추론할 수 있도록 했습니다.

 

3. TypeScript는 이 매핑 함수의 반환 타입을 (T | U)[]로 추론하므로, 타입 어서션 없이 튜플을 만들기 위해 앞서 소개한 tuple 유틸리티를 이용했습니다.

 

interface Array<T>를 선언할 때 전역 Array 네임스페이스에 추가했습니다.

 

즉, 다른 파일에서 zip.ts를 임포트 하지 않아도 [].zip을 이용할 수 있으리라 짐작됩니다.

 

하지만 Array.prototype에 기능을 추가하려면 zip을 사용하는 모든 파일이 zip.ts를 먼저 로드해야 합니다.

 

프로젝트에서 zip.ts를 명시적으로 제외하도록 tsconfig.json을 수정합니다.

 

그러면 이 기능을 사용하는 쪽에서 명시적으로 임포트해야 합니다.

{
  *exclude*: [
    "./zip.ts"
  ]
}

그러면 다음처럼 안전하게 zip을 사용할 수 있습니다.

import './zip'
[1, 2, 3]
.map(n => n *2) // number[]
.zip(['a', 'b', 'c']) // [number, string]

위의 코드를 실행하면, 배열에 매핑과 압축(zip)을 차례로 실행한 결과를 얻을 수 있습니다.

import './zip'
[1, 2, 3]
.map(n => n *2) // number[]
.zip(['a', 'b', 'c']) // [number, string]
[
  [2, 'a'],
  [4, 'b'],
  [6, 'c']
]
반응형