2024. 12. 24. 18:24ㆍProgramming Language/Node.js
타입 스크립트에서 패키지의 타입 선언이란 타입스크립트가 외부 라이브러리나 패키지가 제공하는 기능을 이해할 수 있도록 도와주는 것을 의미하는 것으로 쉽게 말하자면 이 라이브러리에 어떤 함수가 있고, 이 함수는 어떤 값을 받고, 어떤 값을 반환하는지 알려주는 설명서라고 생각하면 된다.
이 타입 선언 파일은 주로 .d.ts라는 확장자를 가진 파일로 제공된다.
왜 패키지 타입선언이 필요할까
1. 안전한 코드 작성
외부 라이브러리를 사용할 때 잘못된 값을 넣거나 잘못된 방식으로 사용하면 컴파일러가 알려준다.
import _ from "lodash";
const numbers = [1, 2, 3];
_.reverse(numbers); // 이건 정상 작동
_.sum(numbers, 5); // 에러: sum은 하나의 배열만 받을 수 있어요
2. 자동완성 기능
타입선언으로 인해 함수의 이름이나 매개변수를 자동으로 추천받을 수 있다.
import _ from "lodash";
const result = _.chunk([1, 2, 3, 4], 2);
// IDE가 `chunk` 함수의 매개변수와 반환값을 알려줌
3. JavaScript 라이브러리를 쉽게 사용
대부분의 JavaScript 라이브러리에는 타입 정보가 없기에 타입 선언을 통해서 avaScript 라이브러리도 TypeScript처럼 사용할 수 있게 된다.
패키지 타입선언 방법
패티지를 선언하기 이전에 테스트에 사용할 lodash라는 패키지를 하나 받아보자.
이렇게 패키지를 받아준 다음에 main.ts 파일 내부에서 패키지를 import 해보면
이렇게 에러를 발생시키는 것을 볼 수 있다.
이는 node_module 내부에 있는
lodash 패키지 내부에 있는 package.json을 보면
그 내부에 main이라는 옵션을 보면
이렇게 lodash의 메인파일은 lodash라는 이름의 js파일임을 알 수 있는데, 이는 main.ts파일에서 lodash의 메인인 lodash.js파일을 가져와야 하기 때문에 lodash.js파일 안에는 자바스크립트이기 때문에 타입에 관련되 내용이 없기에 에러가 발생하는 것이다.
모듈을 불러오는 과정에 대해서
모든 Node.js 모듈의 package.json에는 main이라는 필드가 있는데 이 필드는 해당 패키지를 가져올 때(import 또는 require) 기본적으로 로드할 파일을 지정한다.
예를 들어, lodash의 package.json에 다음과 같이 설정되어 있다면
{
. . . .
"main": "lodash.js",
. . . .
}
이는 import _ from 'lodash' 또는 require('lodash')를 실행하면, node_modules/lodash/lodash.js 파일이 로드된다는 뜻이다.
여기서 타입스크립트는 정적 타입 검사를 통해 런타임 전에 코드의 오류를 발견하는데 lodash.js는 JavaScript 파일이에 타입 정보를 제공하지 않고 타입스크립트는 해당 모듈을 암시적으로 any 타입으로 간주하게 된다.
이런 경우, TypeScript는 "Could not find a declaration file for module 'lodash'"라는 경고를 출력한다.
이를 해결하기 위해서 먼저 src아래에 파일을 하나 생성하는데 이때 이름은 lodash.d.ts 파일로 생성한다.
.d.ts로 생성하는 파일은 타입 선언(Type Declaration)을 정의하는 파일로 dts파일이라고 부르는데 이는 JavaScript로 작성된 코드에 대해 타입 정보를 제공하거나, 외부 라이브러리의 타입을 정의하는 데 사용된다.
정리하자면, 코드 구현이 없는 타입 정의만 포함된 파일로 타입스크립트가 컴파일 과정에서 타입검사를 참고할 자료와 같다고 생각하면 된다.
dts 파일은 보통 여러가지의 타입에 대한 정의를 할 수 있으나 모듈에 대해서만 보자면 간단하게 사용하면
declare moudule "모듈명" {
으로 시작한다.
이는 사용하고자 하는 모듈의 명과 동일하게 작성해줘야 한다.
예를 들어 우리가 사용하려는 lodash의 경우는
declare module 'lodash' {
와 같이 작성해야 한다.
그리고 이 내부는 이제 원하는 대로 작성해도 되나
마지막에는
export default 무언가
와 같이 내부에 작성한 값들을 반환할 수 있도록 반환해줘야 한다.
그래야 우리가 ts파일 내부에서 import 혹은 require한 모듈로써 사용이 가능하다.
lodash를 한번 dts파일을 한번 구성해보자면
declare module "lodash" {
interface Lodash{
reverse: (number: number[]) => number[];
sum : (number: number[], index : number) => number
}
const _ : Lodash;
export default _
}
이렇게 구성할 수 있다.
인터페이스를 통해서 Lodash에서 사용할 함수의 매개변수의 타입, 반환 타입을 설정해주고 이를 _라는 변수를 통해 객체로 만들고 이를 export default를 통해서 내보낸다.
export 와 export default의 차이
export로 내보낸 경우 해당 값의 명칭을 그대로 사용해줘야만 한다.
예를 들어
export function reverse<T>(array: T[]): T[] {
return array.reverse();
}
export function sum<T>(array: T[]): T[] {
return array.reduce((acc, num) => acc + num, 0);
}
이렇게 함수를 export 했다고 한다면
import 하는 쪽에서는 이 함수를
import { reverse, sum } from './example';
이렇게 받아줘야만 한다.
export default의 경우는
export default function reverse<T>(array: T[]): T[] {
return array.reverse();
}
이렇게 반환했을때
// 다른 파일에서 사용하기
import myReverse from './example'; // 기본값이므로 이름 자유롭게 지정 가능
와 같이 맘대로 이름을 지정해서 선언 해줄 수 있게 된다.
대신 export default의 경우는 하나의 값만 내보낼 수 있기 때문에 한 모듈에서 여러개의 값을 내보내고 싶다면 export를 여러번 사용하는 방법을 사용해야만한다.
물론 export default와 export를 혼용하는 것 또한 가능하다.
이때 import를 한다면 export로 내보낸 경우는 명시적으로 명칭을 지정해줘야만 해당 값을 가져와 사용할 수 있게 된다.
물론 export default를 어떻게 구성해서 내보내느냐에 따라서 하나의 값이 여러가지 값을 포함하고 그걸 사용할 수 도 있다.
위에 작성한 인터페이스를 통해서 객체를 선언한 다음에 그걸 내보내는 방법과 같이 말이다.
그러면
이렇게 lodash에서 에러가 사라진 것을 볼 수 있다.
위에서 선언한 함수를 사용해보면
정상적으로 사용이 되고 그 값도
정상적으로 출력하는 것을 볼 수 있다.
주의해야 할 점은 lodash라는 패키지에서 제공하는 모든 함수를 다 사용할 수 있는게 아니라 dts파일에 명시해둔 함수만 사용할 수 있다는 점이다.
lodash에는 camelCase나 snakeCase와 같은 함수들이 존재하는데 이걸 그냥 main.ts에서 사용해보면
이렇게 Lodash라는 타입에는 존재하지 않는다고 컴파일 에러를 발생시키는 것을 볼 수 있다.
이렇게 해당 모듈에 사용하고자 하는 함수가 있다면 모두 dts파일 내부에 정의해줘야만 하는 번거로움이 있다.
또 하나 dts파일을 통해서 패키지의 타입을 선언해줬다면 해당 파일의 명칭이 바뀌면 import한 소스에 해당 파일의 경로를 작성한 코드에 문제가 있게 된다.
이렇게 lodash.d.ts라는 파일을 만든 경우 from을 할때 lodash를 ./(현재파일의 위치에 있는 lodash)의 위치에서 lodash를 찾게 되는데 만약 lodash.d.ts파일의 명칭을 main.d.ts로 변경된다고 가정해보면 이제 이 lodash.d.ts파일을 찾지 못하기 때문이다.
이때 사용할 수 있는게 삼중 슬래시 지시자라는 건데 import하는 가장 위쪽에 /를 세개 입력해주고
<>내부에 reference를 넣어주고 속성으로 path를 넣어준 후에 값을 ./main.d.ts를 넣어주면
이렇게 에러가 사라지는 것을 볼수 있다.
*주의해야 할 점은 <와 reference 사이에 공백이 존재하면 인식을 안한다.
이렇게 reference라는 이름의 태그를 참조 태그라고 부른다.
이렇게 /// 삼중 슬래시 지시자를 통해서 reference라는 참조 태그를 제공하고 이 경로를 패키지의 타입이 명시된 dts파일로 연결해주면 된다.
그런데 우리가 모든 모듈을 사용할때 이것 처럼 매번 dts파일을 선언해줄 수는 없는 노릇 아닌가?
그렇기 때문에 사실 패키지의 타입에 대한 선언을 모두 해둔 것을 가져와 사용할 수 있는 방법이 있다.
구글에 'definitely typed'를 입력해보면 가장 첫번째 보이는 깃허브 링크로 들어가보면
이안에 전세계의 많은 개발자들이 자바스크립트의 모든 타입을 다 정의해뒀기에 이걸 가져다 사용하기만 하면된다.
사용하는 방법은 아래쪽에 작성되어 있는데
이렇게 @types/패키지명 과 같이 사용해서 내용을 받으라고 하는 것을 볼 수 있다.
그런데 모든 패키지가 이렇게 타입을 정의해두지 않았기 때문에 우리가 사용하려는 패키지가 정의가 존재하지 않을 수 도 있다.
그렇기에 이를 알아봐야 하는데 그 방법은 npm info 명령을 통해서 확인할 수 있다.
우리 프로젝트의 터미널을 열고
npm info @types/lodash
와 같이 입력해보자.
이렇게 lodash에 대한 타입스크립트 정의가 존재한다는 것을 알수 있다.
존재하지 않는다면
이렇게 404에러를 발생시킨다.
아무튼 @types/lodash를 받아 보면
바로 에러가 사라지는 것을 볼 수 있다.
이렇게 받은 @types/lodash는 어떻게 인식하냐면
node_modules 아래에 @types를 보면
이렇게 lodash가 존재하는 것을 볼 수 있다.(현재는 types에 lodash밖에 존재하지 않아서 바로 lodash의 definitely type으로 들어가진 것임, 폴더로 존재하고 있음)
그리고 이 @types를 어떻게 패키지에서는 인식하고 가져가냐면
이 tsconfig.json 내부에
이렇게 정의 되어 있는데 여기에 typeRoots라는 속성을 정의하지 않으면(따로 지정하지 않는다면) 기본적으로 "./node_modules/@types"가 기본값으로 설정되어 진다.
그렇기 때문에 어떤 설정 없이도 definitely type을 가져와 인식할 수 있는 것이다.
요즘은 이렇게 대부분의 자바스크립트 패키지들(유명한)은 대부분 definitely type이 존재하거나 패키지 자체에 타입에 대한 선언이 이미 되어 있는 경우가 대부분이라 이렇게 패키지의 타입을 선언해서 사용하는 경우가 많지는 않을 것이다.
'Programming Language > Node.js' 카테고리의 다른 글
Node.js로 웹 개발하기 - 01. 웹 서버 만들기 (2) | 2024.12.26 |
---|---|
TypeScript에 대한 간단한 정리 - 13. tsconfig.json의 구성 옵션 (0) | 2024.12.24 |
TypeScript에 대한 간단한 정리 - 11. 제네릭 - 함수 / 클래스 / 인터페이스 (0) | 2024.12.23 |
TypeScript에 대한 간단한 정리 - 10. 클래스와 접근 제어자 (1) | 2024.12.21 |
TypeScript에 대한 간단한 정리 - 09. 함수 - 오버로딩(Overloading) (0) | 2024.12.20 |