의존성 분리를 위해 미러 타입 사용하기

직접 TS로 라이브러리를 작성하여 공개할 때는 필요 이상으로 의존성을 갖는 것을 피해야 합니다. 이를테면, 아래처럼 CSV 파일을 파싱하는 함수를 만든다고 할 때, NodeJS 사용자를 위해 매개변수에 Buffer 타입을 허용하였다고 가정합시다.

function parseCSV(contents: string | Buffer): {[column: string]: string}[]  {
  if (typeof contents === 'object') {
    // It's a buffer
    return parseCSV(contents.toString('utf8'));
  }
  // ...
}

여기서 쓰인 Buffer 타입은 NodeJS에 대한 타입 선언을 설치하여 얻을 수 있는데, 이 경우 작성한 라이브러리에 대한 타입 선언도 포함됩니다. 이는 @types/node에 의존하기 때문에, 결국 devDependencies로 포함하게 됩니다. 결국 이에 따라 @types와 무관한 JS 개발자나, NodeJS를 프로젝트에 이용하지 않는 TS 개발자의 경우 사용하지 않는 모듈을 포함해야 하는 문제가 생겨납니다.

구조적 타이핑을 활용하세요

이 경우에 저희가 초기에 다루었던 구조적 타이핑을 적용할 수 있습니다. 사용할 타입을 완전히 가져다 쓰는 대신, 필요한 메서드와 속성에 대해서만 별도로 타입을 작성하는 방법을 이용할 수 있습니다.

예를 들어, 위의 예시에서 Buffer의 경우는 다음과 같이 간략하게 타입을 선언하여 대체할 수 있습니다.

interface CsvBuffer {
  // parseCSV에서 쓰이는 함수에 대해서만 타입 선언을 합니다.
  toString(encoding: string): string;
}

구조적 타이핑의 관점에 따라, 해당 타입은 Buffer와도 호환되기 때문에 실제로 NodeJS 프로젝트에서 Buffer 인스턴스로도 parseCSV를 호출할 수 있습니다.

한편 다른 라이브러리에서의 타입 선언 대부분을 추출해야 하는 상황이라면, 차라리 명시적으로 @types 의존성을 추가하는 게 낫습니다.