@types는 @types/<module>
와 같이 서드파티 모듈의 타입 명세를 모아둔 타입 모듈에서 자주 볼 수 있지만 일부 개발자들이 이걸 개발 환경에서 같은 이름으로 쓰는 모습이 보였다.
@types 디렉토리는 무려 material icons에서 아이콘까지 줄 정도로 꽤나 유명한 방식이고 패턴이다. 하지만 물어본 결과 대부분 개발자들이 아무 생각없이, 즉 자세한 원리나 이유 없이 그렇게 쓰고 있었다. 왜냐하면 진짜로 원리가 없으니깐!
하지만 여기선 @types라는 이름의 디렉토리에 대한 타입스크립트의 특별한 처리가 아니라 왜 타입을 모아두는가, 왜 .d.ts 파일을 사용하는가에 대한 이유를 나열할텐데, 대표적으로 두 방식과
공통적으로 아래와 같은 장점이 있다.
ErrorResponse
와 같은 타입으로 일관되게 사용해왔더라면 해당 타입만 편집하면 된다. 이것이 왜 타입 선언 파일의 장점이냐면 바로 분산된 사용처가 한 곳으로 모여들었기 때문이다. 가끔 이 타입이 있는지도 모르고 다시 선언하는 경우가 있는데, 이런 일을 막아준다.장황하게 늘여놓았지만 결국 다들 납득하는 당연한 부분이고 이러라고 @types를 쓰는 것이다. 패턴의 장점은 확실하다. 그럼 문제는 무엇인가?
앞서 말한 타입 모듈 모음 방식을 잘못되게 사용하는 경우가 있는데, 우선 아래 사진을 보라.
스크립트에서 export문을 사용하지 않으면 최상위 스코프 위치는 스크립트 모듈 스코프 -> 전역 스코프로 이전된다. 그러므로:
- export문이 없는 스크립트에선 사용되지 않은 const문이 비활성 처리되지 않는다. 즉 어딘가에서 사용되고 있다. (전역)
- 다른 스크립트에서 export문이 없는 스크립트에서 선언한 변수를 참조할 수 있다. 즉 같은 스코프다.
.d.ts
또한 export문을 배제하면 전역 스코프 이므로 자연스레 타입들은 전역 declare가 된다.
+ 그래서 전역 스코프에서 타입 명시를 할 때 declare하는건 불필요하다.
아마 자바스크립트를 사골까지 우려먹었더라면 첫번째와 두번째, 즉 모듈과 스코프의 관계는 알고 있을 것이다. 나 또한 잠시 잊었으나 알고 있었다. 하지만 중요한건 이것이 타입과 상호작용한 결과, 즉 전역 타입 명시다.
과거의 난 위와 같은 방식으로 직접 타입을 export하여 import type하는 방식을 사용했으나, export/import를 하지 않음으로써 모듈의 최상위 스코프를 전역 스코프로 끌어올린다면 굳이 import하지 않더라도 우리가 Number
를 import 없이 쓰는 것마냥 사용할 수 있다.
즉, 불필요한 타입 모듈화는 코드를 더럽게 만든다.
전역 스코프의 타입 명시는 머릿 속에서 대혁명처럼 느껴졌다. 하지만 이 전역 명시도 필요할 때 적절하게 써야 하니, 그 오용의 예를 들자면...
이전 글에서 가끔 출연하던 declare module
문은 위와 같이 모듈의 인터페이스를 declare함으로써 타입 보강을 한다.
따라서 declare module
문에 있던 field2 속성은 ./ov1 모듈의 style 인터페이스 타입에 보강 되었다.
이것이 일반적인 모듈 스코프에서 declare module
문의 사용처인데, 문제는 이게 전역 스코프에선 타입 보강이 아니라 타입 선언이 된다는 것이다.
예를 들자면
NextAuth는 이와 같이 모듈 스코프에서 선언된 상태였는데
전역 스코프에서 해당 모듈을 통째로 재선언하는 바람에 진짜 next-auth
에 있던 NextAuth 함수는 씹혀버렸다.
타입 체커의 동작 알고리즘을 정확히 알 수가 없지만 확실한건 전역 스코프에서 declare module를 하면 해당 모듈 타입이 통째로 갈아치워진다라는 것이다.
처음에 원하던대로 타입 보충, 즉 기존 타입에 덧씌우기/주입하기만 하고 싶다면 선언할 모듈 레벨을 전역에서 블록으로 내리면 된다.
난 override.d.ts
라는 타입 보강용 타입 모듈을 따로 만들었다.
+ 특정 모듈에 국한되는 사정이 아니라 타입 보강이 쓰이는 어디든지 해당된다. 가령 process.env
라든지...
도대체 어디서 시작된건지 모를 고정관념과 오해인데 tsconfig
의 complierOptions
에 있던 typeRoots
를 비롯한 types
속성은 node_modules/@types
에 있는 타입 모듈들 중 전역 스코프에 포함될 모듈들을 특정하는 배열 속성이다. 타입 모듈들은 기본적으로 포함되어 있는데 이 이상한 오해로 인해 @types/모듈
을 설치해도 가끔 제 일을 못하던 것이였다.
예전에 @types/node
를 설치하고 node_modules
에 있는걸 확인하고도 타입이 없다고 말하니 너무 억울해서 파해쳐도 힘들어서 그냥 넘어갔던 적이 잇었는데 이제 와서 돌아보니 이런 원인이 있었던 것을 깨달았다.
역시 고정관념이 가장 무섭다..
참고로 두 속성의 차이는 공식 문서에도 있지만 하위 모듈들도 전역에 포함시키냐 아니냐다.
결론적으로 개발 환경의 @types와는 관련이 전혀없다.
@types는 깃허브의 README나 prettier의 .prettierrc처럼 typescript의 .tsconfig같은 존재가 아니다. 그냥 말 그대로 types가 있는 디렉토리일 뿐이다.
그러므로 사실이 아니다.
모듈 경로는 꽤나 특이하다.
asdf/index.d.ts
와 @asdf/index.d.ts
는 asdf.ts
와 같게 취급된다.
하지만 그렇다고 둘이 합쳐지는건 아니라 필요하면 import해야 하는 점은 여전하다.