Typescript - 타입선언과 @types

이펙티브 타입스크립트 6장

Posted by Juri on July 18, 2022

외부 API를 사용하다보면 타입 선언이 없는 js 기반의 API를 쓸 일이 있다. 자체적으로 타입을 선언해보기도 하고 typescript 커뮤니티에서 제공하는 @types 라이브러리를 사용해보기도 하면서 삽질도 많이 하고 구글링도 많이 한 결과, 타입스크립트의 근본을 더 공부할 필요성을 느껴 책을 뒤져보았다.

이번 포스팅에서는 타입스크립트에서 의존성이 어떻게 동작하는지 알아보고 의존성 관리를 하다가 맞닦뜨릴 수도 있는 문제의 해결방법을 찾아보도록 한다.

devDependencies에 typescript와 @types 추가하기

npm은 3가지 종류의 의존성을 구분해서 관리하며 각각의 의존성은 package.json파일 내에서 확인할 수 있다.

  • dependencies 현재 프로젝트를 실행하는 데 필수적인 라이브러리를 포함한다. 프로젝트를 npm에 공개해 다른 사용자가 해당 프로젝트를 설치한다면 dependencies에 포함된 라이브러리가 설치된다.
  • devDependencies 현재 프로젝트를 개발하고 테스트하는 데 사용되지만 런타임에는 필요없는 라이브러리를 포함한다. 프로젝트를 npm에 공개해 다른 사용자가 해당 프로젝트를 설치한다면 devDependencies에 포함된 라이브러리들은 제외된다.
  • peerDependencies 런타임에 필요하지만 의존성을 직접 관리하지 않는 라이브러리를 포함한다.

타입정보는 런타임에 존재하지 않기 때문에 타입스크립트와 관련된 라이브러리는 일반적으로 devDependencies에 속한다.

타입스크립트 프로젝트에서 공통적으로 고려해야 하는 의존성으로 두가지가 있다.

  1. 타입스크립트 자체 의존성
  2. 타입 의존성

타입스크립트 자체 의존성

프로젝트 팀원 모두가 항상 동일한 버전의 타입스크립트를 설치하리라는 보장이 없고 프로젝트 셋업시 별도의 단계가 필요해 타입스크립트를 시스템 레벨로 설치하는 것 보다 devDependencies에 넣는 것이 좋다. 커멘드 라인에서 npx을 사용해 devDependencies에 설치된 타입스크립트 컴파일러를 실행할 수 있다.

타입 의존성

사용하려는 라이브버리에 타입 선언이 포함되어 있지 않아도 DefinitelyTyped에서 타입 정보를 얻을 수 있다. DefinitelyTyped의 타입 정의들은 npm 레지스트리의 @types 스코프에 공개된다. @types 라이브러리는 타입 정보만 포함하므로 원본 라이브러리 자체가 dependencies에 있더라도 @types 의존성은 devDependencies에 있어야 한다.

1
2
$ npm install react
$ npm install --save-dev @types/react

리액트의 타입 선언과 리액트를 의존성에 추가하려면 위와 같이 실행해야 한다.

1
2
3
4
5
6
7
8
9
{
  "devDependencies": {
    "@types/react": "^16.8.19",
    "typescript": "^3.5.3"
  },
  "dependencies": {
    "react": "^16.8.6"
  }
}

타입스크립트에서 추가로 고려해야 하는 세가지 버전

타입스크립트를 사용하면 아래의 세 가지 사항을 추가로 고려해야 한다.

  1. 라이브러리의 버전
  2. 타입 선언(@types)의 버전
  3. 타입스크립트의 버전

세 가지 버전 중 하나라도 맞지 않으면 의존성과 상관없어 보이는 곳에서 오류가 발생할 수 있다. 이러한 오류의 원인을 파악하기 위해서는 타입스크립트 라이브러리 관리의 메커니즘을 이해해야 한다.

타입스크립트에서 의존성을 사용할 때 일반적으로 특정 라이브러리를 dependencies로 설치하고 타입 정보는 devDependencies로 설치한다.

1
2
3
4
5
$ npm install react
+ react@16.8.6

$ npm install --save-dev @types/react
+ @types/react@16.8.19

메이저 버전과 마이너 버전(16.8)이 일치하지만 패치 버전은 일치하지 않는다. @types/react의 16.8.19는 타입 선언들이 리액트 16.8 버전의 API를 나타낸다는 것을 의미한다. @types 모듈의 패치 ㅓ전은 버그나 누락으로 인한 수정과 추가에 따른 것으로 라이브러리 자체보다 타입 선언에 더 많은 업데이트가 있었음을 알 수 있다.

실제 라이브러리와 타입 정보의 버전이 별도로 관리되는 방식은 몇 가지 문제점이 있다.

  1. 라이브러리를 업데이트 했지만 실수로 타입 선언은 업데이트하지 않는 경우 (라이브러리 > 타입 선언) 라이브러리 업데이트와 관련된 새로운 기능을 사용하려 할 때마다 타입 오류가 발생한다. 일반적으로 타입 선언을 업데이트하여 라이브러리와 버전을 맞춰 해결할 수 있다. 그러나, 업데이트할 타입 선언의 버전이 없는 경우 사용하려는 새 함수와 메서드의 타입 정보를 프로젝트 자체에 추가하거나 타입 선언의 업데이트를 직접 작성하고 공개하여 커뮤니티에 기여할 수 있다.

  2. 라이브러리보다 타입 선언의 버전이 최신인 경우 (라이브러리 < 타입 선언) 타입 정보없이 라이브러리를 사용하다가 뒤늦게 타입 선언을 설치하려고 할 때 발생한다. 타입 체커는 최신 API를 기준으로 코드를 검사하지만 런타임에 실제로 쓰이는 것은 과거 버전이 된다. 라이브러리와 타입 선언의 버전이 맞도록 라이브러리의 버전을 올리거나 타입 선언의 버전을 내린다.

  3. 프로젝트에서 사용하는 타입스크립트 버전보다 라이브러리에서 필요로 하는 타입스크립트 버전이 최신인 경우 현재 프로젝트보다 라이브러리에게 필요한 타입스크립트의 버전이 높으면 @types 선언 자체에서 타입 오류가 발생한다. 프로젝트의 타입스크립트 버전을 올리거나 라이브러리 타입 선언의 버전을 내리거나 declare module 선언으로 라이브러리의 타입 저오를 없앤다.

1
$ npm install --save-dev @types/lodash@ts3.1

이와 같이 타입스크립트의 특정 버전에 대한 타입 정보를 설치할 수도 있다.

  1. @types 의존성이 중복되는 경우 만약 @types/bar가 현재 프로젝트와 호환되지 않는 버전의 @types/foo에 의존한다면 npm은 중첩된 폴더에 별도로 해당 버전을 설치하여 문제를 해결하려고 한다. 전역 네임스페이스에 타입 선언이 존재하면 중복된 선언, 또는 선언이 병합될 수 없다는 오류가 발생한다.