TypeScript 연습 <1> -17-
________________________________________________________________________
TypeScript 블록체인 <2> - JS -> TS
< 타입스크립트로 블록체인 만들어보기 - 자바스크립트 파일을 타입스크립트 프로젝트에서 사용하기 >
폴더 : kimminsoo -> NomadCoders -> typechain
< JSDoc >
1.
________________________________
< 타입스크립트 안에 자바스크립트를 허용하기 >
tsconfig.json 에 가서 "compilerOptions" 에 < “allowJs” : true > 옵션을 추가.
——
// tsconfig.json
"compilerOptions": {
"outDir": "build",
"target": "ES6",
"lib": ["ES6", "DOM"],
"strict": true,
"allowJs": true
}
——
=> 타입스크립트 안에서 자바스크립트를 허용한다 라는 의미. 기본값은 False 인듯.
이제 타입스크립트가 Js 파일 안에 들어와서 함수를 다 불러올 수 있게 되었어.
추가로, 현재 src 파일에는 index.ts, myPackage.js, myPackage.d.ts 이렇게 세 개의 파일이 존재할거야.
기존에는 index.ts 에서
——
import {init, exit} from "myPackage";
——
이렇게 import 구문을 입력하여 myPackage.d.ts 에서 declare module "myPackage" {} 를 가져왔지,
myPackage.d.ts 를 일종의 작은 Npm 패키지로 취급하는 방법 이었어.
이제는 타입스크립트 파일인 myPackage.d.ts 이 아니라 자바스크립트 파일인 myPackage.js 에서 모듈을 가져와 볼거야.
=> 타입스크립트 파일 안에서 자바스크립트 파일을 불러와 사용하는 법
우선 index.ts 의 import 구문을 다음과 같이 변경.
——
import {init, exit} from "./myPackage";
——
=> "myPackage" 를 "./myPackage" 로 변경했어.
현재 상태에서는 “myPackage.d.ts 은 모듈이 아니다” 라면서 에러를 띄우지.
exports 모듈이 아닌 declare module(정의 모듈) 이기 때문이야.
지금 어떤 상황인지 명확하게 인지를 해보자.
첫 번째. index.ts에서 import를 할 때, “myPackage" 과 "./myPackage" 의 차이는 무엇인가?
=> “./myPackage” 는 “파일” 을 찾는 거고, “myPackage" 는 “정의 모듈” 을 찾는거야.
“./myPackage” 로 파일을 찾아 오면, 해당 파일 안에서 exports 된 모듈들을 호출하여 사용할 수 있지.
“myPackage" 로 ‘정의 모듈’ 을 찾으면, 해당 모듈을 signature 삼아 참고하여 함수를 구현해 볼 수 있어.
즉, 기존의 “myPackage" 에서 “./myPackage” 로 바꾸니,
“정의 모듈” 을 찾고 있던 걸 “파일” 을 찾도록 단위가 변경되어 버린거야.
두 번째. index.ts 에서 myPackage 라는 키워드로 파일 혹은 모듈을 찾으려 하면, 무조건 “myPackage.d.ts” 를 대상으로 찾는다.
=> “myPackage" 를 찾고 있을 때에는 “myPackage.d.ts” 의 declare module "myPackage" 를 인식해.
그리고 “./myPackage” 로 import 하려 하면 여전히 “myPackage.d.ts” 를 찾으려도 하는데,
문제는 “myPackage.d.ts” 에는 일반적인 “호출해서 사용 가능한” 모듈을 exports 하고 있지 않아.
즉 대상은 똑같이 “myPackage.d.ts” 인데, “myPackage”이냐 “./myPackage” 이냐에 따라서
import 하는 대상이 “정의 파일의 정의 모듈이냐, 혹은 호출 및 사용 가능한 Exports 냐” 로 바뀌는 거야.
그래서, 지금 우리는 코드를 어떻게 바꾸고 싶은건데?
우리의 목표는 “자바스크립트 파일을 타입스크립트 파일 안에서 사용” 하는 것.
즉 우리가 불러올 것은 “정의 파일의 정의 모듈” 이 아니라 “자바스크립트 파일의 module.exports 로 사용할 수 있는 대상” 인거야.
따라서 우리는 “./myPackage’ 로 “myPackage.js” 파일을 불러와서 해당 파일 안에 있는 함수를 호출 및 사용할 수 있어야 해.
그렇다면, 우선 myPackage.d.ts 파일이 지금 myPackage.js 랑 이름이 곂치니까,
myPackage.d.ts 파일의 이름을 사용하지 않는다는 의미에서 dontUse_myPackage.d.ts 로 바꾸고,
index.ts 에서 “./myPackage” 라고 하면 무조건 myPackage.js 파일을 찾도록 변경해야 해.
myPackage.d.ts 파일 이름을 “dontUse_myPackage.d.ts” 로 바꿔준 뒤,
index.ts 의 다음 구분을 확인하면 에러가 사라져 있음을 알 수 있어.
——
import {init, exit} from "./myPackage";
——
=> 왜 에러가 사라진 건가?
위에서 언급했듯이 index.ts 에서는 "./myPackage" 로 파일을 찾으려 하면 무조건 myPackage.d.ts 를 먼저 찾았었는데,
myPackage.d.ts의 파일이름을 바꿔주니 이제는 myPackage.js 라는 자바스크립트 파일을 찾게 된거지.
물론 여전히
——
import {init, exit} from "myPackage";
——
라고 입력한다면, 같은 폴더의 경로 안에서 “myPackage” 라는 “정의 모듈” 을 찾으려 하기에,
import 대상이 dontUse_myPackage.d.ts 안에 있는 declare module "myPackage" 로 바뀌게 돼.
단지 이전에는 "./myPackage" 로 찾으면 무조건 myPackage.d.ts 파일을 찾으려 했던 것을
정확하게 myPackage.js 파일을 찾도록 변경해 준 것일 뿐.
myPackage.js 파일을 찾도록 변경해 준 상태에서 index.ts 의 Import 구문의 Init 에 마우스를 호버하면 다음과 같이 출력이 돼.
——
// index.ts
import {init, exit} from "./myPackage";
// Init 에 마우스 호버. Call signature 를 추론해주고 있어.
(alias) function init(config: any): boolean
import init
——
=> 뭔 소리냐? 정확하게 myPackage.js 파일의 Init 을 가져오고 있다는 뜻.
myPackage.js 에서는 타입을 정의해 주지 않았으니 (당연해. 자바스크립트 파일 이니까)
매개변수인 config 가 any 상태인거지.
리턴이 boolean 인 이유는 myPackage.js의 init 함수에서 return true 를 해주고 있으니 타입을 추론하고 있는 것이고.
exit 도 마찬가지.
반대로, 잠시 "./myPackage" 를 다시 "myPackage" 로 돌려볼까?
——
// index.ts
import {init, exit} from "myPackage";
// Init 에 마우스 호버
(alias) function init(config: Config): boolean
import init
——
=> "./myPackage" 를 "myPackage" 로 되돌리자 dontUse_myPackage.d.ts 파일의 declare module 를 다시 찾게 되었어.
즉 파일이 아닌 정의 모듈을 대상으로 찾아서 Import 할 때에는, 파일 이름이 중요한 게 아니라 모듈의 이름이 대상임을 확인할 수 있지.
실제로 dontUse_myPackage.d.ts 파일에서는 init 의 signature 에서 매개변수를 Config 타입이라 명시하고 있기에 그대로 보여주고 있어.
***
어쨌든 중요한 것은 뭐냐!
tsconfig.json 파일에서 "allowJs": true 라는 옵션을 통해서 < 타입스크립트 파일 안에서 자바스크립트 파일을 불러올 수 있도록 허용 > 했다는 것.
따라서 index.ts 에서 myPackage.js 라는 자바스크립트 파일의 exports 된 대상들을 불러올 수 있게 된 것이지.
옛날 코드는 자바스크립트로 작성하고, 새로운 코드는 타입스크립트로 작성하는거지.
즉, 자바스크립트로 만들어진 패키지나 라이브러리들의 함수와 각종 API 들을, 굳이 새롭게 Signature 를 정의해 주지 않더라도
타입스크립트 파일로 불러와 사용할 수 있다는 것. 그 과정에서 타입스크립트가 알아서 호출 시그니쳐(call signature) 의 타입을 추론해 줘.
2.
________________________________
< JSDoc의 도웅믈 받아서 자바스크립트 파일을 수정하지 않고, 타입스크립트의 보호를 받게 하기 >
하지만!
타입스크립트 파일이 자바스크립트 파일을 확인하게도 하고 싶고, 완전히 타입스크립트로 이전하고 싶지는 않다면?
=> 이게 무슨 말인가.
만약 코드가 몇 천 줄이나 되는 프로젝트가 있다고 가정하자.
그럼 일일이 API 들을 확인해 가면서 export function init 이런 식으로 export 해주고 수정해주고 파일 또 새로 만들고…
경우에 따라서 필요하다면 타입도 명시해주고…. 그러긴 힘들잖아.
( 지금은 Init, exit 라는 간단한 함수 두 개 뿐이니 괜찮지만, 나중에 함수가 복잡해지면
그냥 불러와서 그대로 사용하기에는 힘들어 질 수도 있어.
또한, 기존에 사용했던 자바스크립트 코드들도 타입스크립트의 보호를 받게 하고 싶어.
그것도 수정 없이. )
즉 그럴 때 우리는 이렇게 생각하겠지.
“파일은 자바스크립트 그대로 두고, 타입스크립트의 보호를 조금 받고 싶은데… “
해결책은 다음과 같아. 타입스크립트가 자바스크립트 파일도 보호하게 해줄 수 있어.
——
// myPackage.js
// @ts-check
export function init (config) { // config 에 에러 발생
return true
}
export function exit (code) { // code 에 에러 발생.
return code + 1;
}
——
=> 이게 뭐고 하니,
타입스크립트 파일한테 자바스크립트 파일을 확인하라고 알리는 거야. 타입스크립트 문법을 전혀 사용하지 않고도!!
자바스크립트 파이를 수정하지 않고, 타입스크립트가 제공하는 보호 장치를 사용할 수 있게 되는 거야.
단, 아직 문제가 있어.
인자인 config 와 code 에 빨간줄이 그이며 에러가 발생했다고 알려주는데,
내용인 즉슨 “매개 변수에는 암시적으로 'any' 형식이 포함됩니다.” 라고.
파일을 확인하라고 알려줄 뿐 아니라, 해당 자바스크립트 함수에도 적용해 달라 라고 추가로 명시를 해야 해.
추가적으로 필요한 것이 하나 더 있어. 바로 JSDoc.
JSDoc은 쉽게 말해서 코멘트로 이루어진 문법. 함수 바로 위에 코멘트를 적어주면 돼.
정해진 문법에 맞춰서 코멘트를 작성해 준다면, 타입스크립트가 이를 인식할 수 있어.
——
// myPackage.js
// @ts-check
/**
*
* @param {*} config
* @returns
*/
export function init (config) {
return true
}
——
=> /* 이렇게 타이핑 하고 * 를 한 번 더 타이핑 하면 JsDoc 문법 이라며 주석이 하나 나오는데, 그 상태에서 엔터 치면
위의 주석 처럼 자동완성 하더라.
——
// myPackage.js
/**
* Initializes the project
* @param {object} config
* @param {boolean} config.debug
* @param {string} config.url
* @returns boolean
*/
export function init (config) {
return true
}
/**
* Exits the program
* @param {number} code
* @returns number
*/
export function exit (code) {
return code + 1;
}
——
=> 해당 함수 바로 위에 JSDoc 문법으로 주석을 달았어.
config 는 object 이며, config 의 debug, url 이라는 각 키들은 키값으로서 boolean, string 타입의 값을 가진다…
return 은 void, 즉 없다.
exit 함수도 마찬가지.
확인하였듯이, 타입스크립트가 ‘주석’ 이라는 매개체를 통해서 자바스크립트 파일을 확인해 주고 있어.
실제로 index.ts 에 가서 Init() 를 작성해 보자.
——
// index.ts
import {init, exit} from "./myPackage";
init({
debug: true,
url: "fdsfdf"
});
// init() 에 마우스 호버 시
(alias) init(config: {
debug: boolean;
url: string;
}): boolean
import init
Initializes the project
@param config
@returns - boolean
——
=> myPackage.js 의 init 함수 원본 위에 내가 남겨둔 JSDoc 문법의 주석이 그대로 반영되어 있음을 확인.
이처럼 우리는 “기존에 작성했던 자바스크립트 코드는 지키고 싶은데, 타입스크립트의 보호를 받게 해주고 싶다” 라면,
JSDoc 문법을 사용해서 기존의 자바스크립트 코드를 수정하지 않고도 타입스크립트의 보호를 받을 수 있게 해줄 수 있어.
1)
"allowJs": true
2)
// @ts-check
3)
/**
*
* @param {*} code
* @returns
*/
위의 세 가지를 통해서 말이지!
이전 챕터에서는 파일 전체를 위한 타입 정의를 생성했어. 파일 하나를 node_module 마냥 만들어서 그 안에서 다 시그니처를 정의해 줬지.
이번 챕터에서는 자바스크립트와 타입스크립트가 함께 작업하는 것을 배워봤어. 자바스크립트도 타입스크립트의 타입 보호를 받을 수 있도록 해줬지.