: 위의 상황은 뭘 나타낼까 ?
상황 : 우리는 자바스크립트로 개발을 할 때 다양한 외부 API(ex DOM API)를 가져다 쓴다(node_modules에 들어가는 모듈 등). 이 때, 타입스크립트를 같이 사용하는 프로젝트라고 했을 때 이러한 모듈들에 대해서 타입스크립트에게 일종의 '설명 과정'이 필요하다. 왜??.. 간략하게 말해보몀ㄴ 타입스크립트는 자바스크립트로 된 모듈들의 타입을 모르기 때문이다. 정말 간단한 예로 앞서 말한 DOM API를 쓴다고 했을 때, 타입스크립트는 DOM API와 관련된 타입을 본래 알고 있지 않다. 그래서 우리는 tsconfig.json 파일에서 이에 대해 타입스크립트에게 설명 과정을 거친다.
위와 같이 lib 속성으로 "dom"을 작성해주면 타입스크립트는 document와 관련된 메서드 혹은 속성의 타입을 알 수 있게 된다. 실제로 어떻게 타입스크립트가 알 수 있는가를 보면, d.ts 확장자를 가진 파일 내에 이에 대해 일일이 설명을 써줬기 때문에 가능하다. 이건 누가 써줬을까?... 이건 로봇이 한게 아닌 우리가 페이스북팀이 만든 리액트를 편하게 쓰는 것처럼 타입스크립트에서 DOM을 쓸 수 있도록 누군가가 작성해준 것이다. (그러니 감사히 쓰자..ㅎㅎ)
아래는 앞서 말한 d.ts 확장자를 가진 lib.dom.d.ts 파일의 일부이다. 아래와 같이 우리가 평소 봤던 querySelector 등과 같은 메서드의 타입에 대해서 작성해놓은 것을 알 수 있다. 다시 말해보면 타입스크립트는 이 파일의 존재를 통해 document에 대해서 알게되고, 이러한 과정이 있기에 ts 확장자 파일에서 document를 자동완성 기능을 통해 사용할 수 있게 된다.
실제로 앞서 tsconfig.json에서 세팅해줬던 lib 내에 "dom"을 지우게되면
위와 같이 document를 인식할 수 없게되고, lib내에 "dom"을 포함시키라는 에러 메시지가 출력된다.
다시 돌아와서, 위의 상황을 파악하기전에 한가지 케이스를 더보고 넘어가자. 우리가 자주 쓰는 Math 모듈은 JS내부에서 제공하는 API이다. 하지만 타입스크립트는 이에 대해서도 설명이 필요하다. 그래서 Math를 마우스 우클릭을 통해 타입스크립트가 이를 어떤 자료를 통해 이해(?)하고 있는지를 확인해보면
lib.es2015.core.d.ts라는 파일 안에
interface Math {
/**
* Returns the number of leading zero bits in the 32-bit binary representation of a number.
* @param x A numeric expression.
*/
clz32(x: number): number;
/**
* Returns the result of 32-bit multiplication of two numbers.
* @param x First number
* @param y Second number
*/
imul(x: number, y: number): number;
/**
* Returns the sign of the x, indicating whether x is positive, negative or zero.
* @param x The numeric expression to test
*/
sign(x: number): number;
/**
* Returns the base 10 logarithm of a number.
* @param x A numeric expression.
*/
log10(x: number): number;
/**
* Returns the base 2 logarithm of a number.
* @param x A numeric expression.
*/
log2(x: number): number;
/**
* Returns the natural logarithm of 1 + x.
* @param x A numeric expression.
*/
log1p(x: number): number;
/**
* Returns the result of (e^x - 1), which is an implementation-dependent approximation to
* subtracting 1 from the exponential function of x (e raised to the power of x, where e
* is the base of the natural logarithms).
* @param x A numeric expression.
*/
expm1(x: number): number;
/**
* Returns the hyperbolic cosine of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
cosh(x: number): number;
/**
* Returns the hyperbolic sine of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
sinh(x: number): number;
/**
* Returns the hyperbolic tangent of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
tanh(x: number): number;
/**
* Returns the inverse hyperbolic cosine of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
acosh(x: number): number;
/**
* Returns the inverse hyperbolic sine of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
asinh(x: number): number;
/**
* Returns the inverse hyperbolic tangent of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
atanh(x: number): number;
/**
* Returns the square root of the sum of squares of its arguments.
* @param values Values to compute the square root for.
* If no arguments are passed, the result is +0.
* If there is only one argument, the result is the absolute value.
* If any argument is +Infinity or -Infinity, the result is +Infinity.
* If any argument is NaN, the result is NaN.
* If all arguments are either +0 or −0, the result is +0.
*/
hypot(...values: number[]): number;
/**
* Returns the integral part of the a numeric expression, x, removing any fractional digits.
* If x is already an integer, the result is x.
* @param x A numeric expression.
*/
trunc(x: number): number;
/**
* Returns the nearest single precision float representation of a number.
* @param x A numeric expression.
*/
fround(x: number): number;
/**
* Returns an implementation-dependent approximation to the cube root of number.
* @param x A numeric expression.
*/
cbrt(x: number): number;
}
장황하지만 다음과 같이 Math에 대해 친절히(?) 타입스크립트에게 각각의 메서드에 대한 타입을 명시해준 것을 볼 수 있다(이또한 인간의 힘...). 어쨌든 결과적으로 여기서 말하고 싶은 것은 타입스크립트에게 JS와 관련된 모듈들을 일일이 일종의 설명을 해줬기 때문에 이해하고, 사용할 수 있는 것이라는 점!
그럼 정말 첫번째 예시로 돌아와서 내가 하려던 것을 살펴보자. 내가 하려던 것은 myPackage라는 말도안되는 나만의 패키지(node_modules에서 가져온다고 가정해보자)를 타입스크립트가 인식할 수 있도록 설명하는 과정을 보여주고자 했다.
그래서 myPackage.js를 만들었고
export function init(config) {
return true;
}
export function exit(code) {
return code + 1;
}
이를 index.ts에서 import해서 쓰고자 했다
import { init, exit } from "myPackage";
init({
url: "true",
});
exit(1);
그랬더니 타입스크립트가 myPackage의 declaration file을 찾을 수 없다며 에러를 리턴했다.
오호,, 즉, 앞선 예시로 설명했던 워딩으로 다시 얘기해보면 이 모듈의 타입에 대해 정의한 파일이 없기 때문에 나는 이 패키지를 이해할 수 없고, 따라서 ts 확장자에서 이 패키지를 쓸 수 없다. 정도로 생각해볼 수 있다.
그래서 앞서 위대하신 개발자분들?이 해주신 것처럼 나도 이 패키지에 대해서 d.ts 확장자 파일을 만들어줬다.
interface Config {}
declare module "myPackage" {
function init(config: Config): boolean;
function exit(code: number): number;
}
위와 같이 작성해주니까 에러가 사라졌다.
그러면,, 매번 JS 모듈을 TS에서 쓰려면 이런식으로 직접 d.ts를 작성해줘야할까? Nope...!. 보통은 예를 들어, moment.js를 가져다 쓴다했을 때
npm install @types/moment
의 명령어로 해당 파일을 다운받아서 문제를 해결할 수 있다고한다. 즉, 모듈을 만든쪽에서 이에 대해 제공을 해준다는 것! 하지만 앞선 방법도 알아두어야하는 것이 이렇게 제공을 안해주는 경우(다운받을 수 없는 경우)가 있기 때문이다.
** 이 때 @types/moment는 도대체 어디서 제공하는걸까?에 대해 알아보면
링크 : https://github.com/DefinitelyTyped/DefinitelyTyped 에서 제공한다(Definitely Typed).
이 깃헙 레포에 가보면 types라는 폴더가 있는데, 그 안에 이전에 설명했던 d.ts 파일이 각 모듈의 이름에 하나씩 들어있는 것을 볼 수 있다. 오늘 설명한 내용을 알고 있다면 결국 이 레포는 타입스크립트에서 JS 패키지를 쓰고자하는 사람들이 해당 패키지에 대해 타입스크립트에게 설명을 하는(d.ts 파일을 통해) 파일을 작성을 해놓고, 그것을 다운받아 쓸 수 있도록 해놓은 것임을 이해할 수 있을 것이다. 그리고 이건 나같은 일반인 개발자(?)도 타입스크립트에서 쓰고자하는 패키지의 타입에 대해 d.ts를 작성해서 올릴 수 있다. 물론 검증의 과정이 있겠지만 그 과정을 통과한다면 이 레포의 기여자가 될 수 있다.