본 글은 타입스크립트 JSDoc
문서에 기반해서 작성되었다.
요즘 확실히 타입스크립트로 고통 받는 개발자의 함성을 자주 듣고 있다. 이미 js의 유연함에 만족하시는 사람은 ts의 필요성을 느끼지 못하기 마련이기에 학습할 필요성을 느끼지 못하기 마련이다. 나 또한 사용하기 전에 그랬다.
또한 갑자기 js에서 ts로 갈아타기는 사실 별거 아니라고 하기에는 타입스크립트 문법과 환경 셋팅법 등 꽤나 학습 비용이 있다. 그리고 초심자를 위해 잘 정리한 자료를 찾는 것도 고된 일이다.
그러나 놀랍게도 js를 사용하면, ts는 자연스럽게 따라온다. 바로 JSDoc, 타입스크립트의 타입 시스템을 자바스크립트의 “주석”만으로 살짜쿵 얹어버리는 마법이다.
이미 친숙한 자바스크립트에 주석만으로 ts 체험을 할 수 있다는 사실만으로 타입스크립트에 대한 장벽을 낮추고 있다.(jsconfig.json
파일 설정을 제외하면)
그리고 가장 중요한 점을 말하지 않았다! 조금씩 타입 "주석"을 적다보면 타입스크립트에 대한 욕구와 자신감이 상승한 자신을 발견할 수 있다! 이는 개발자 dna에 내재되어 있는 강타입에 대한 욕구를 촉진시킬 것이다.
JSDoc 시작 이전에 타입스크립트 기본 타입을 숙지하고 오면 더욱 큰 도움이 된다.
혹은 가벼운 프로젝트를 시작하는데 굳이 타입스크립트를 도입하는것이 귀찮다는 생각이 들 때, 주석을 작성하는 것만으로도 타입을 적용할 수 있다는 강력한 장점이 있다.
나의 경우에는 node.js
에 간단한 스크립트를 작성할 때, node + ts
환경 셋업에 불필요한 시간을 쏟고 있는 자신을 보았을 때 JSDoc을 이용하기로 마음 먹었다. 이는 좋은 선택이었다.
부가적으로 가장 강력한 장점이 한가지 있는데 바로 함수, class, object property 등에 대해 설명을 추가할 수 있다는 점이다.
(프로처럼 보일 수 있다.)
예컨데 요렇게 JSDoc을 작성해주면, code 주석을 awsome하게 확인할 수 있다.
/**
* @description generate static build path for `[category]` in `[category]/page.tsx`
* @see {@link https://nextjs.org/docs/app/api-reference/functions/generate-static-params#generate-params-from-the-top-down generateStaticParams}
* @example
* ```tsx
* // app/[category]/page.tsx
* export async function generateStaticParams() {
* return await blogEngine.generateStaticParamsForCategory()
* }
* ```
*/
public generateStaticParamsForCategory = async () => await this.$buildPath.generateStaticParamsForCategory()
그렇다면 JSDoc은 단점이 없을까?
당연히 있다.
무엇보다 "주석"으로 모든 것을 해결하기 때문에 타입스크립트의 일부 기능들을 사용하는것이 제한적이며, 보다 장황해진다는 단점이 있다. 또한 타입을 불러오는 것 또한 import("타입 경로")라는 구린 문법을 사용해야 되므로, 아쉽기도 하다.
그러나 개인적 사용경험에 따르면 패키지 설치 + 설정 이슈 없이 순수 타입 시스템을 도입할 수 있다는 것은 정말 큰 장점이라는 생각이 들었다. 또한 vscode 에 /**
만 입력해도 함수에 대한 jsdoc을 자동 생성해주는 기능이 있기에 불편한 점이 꽤나 상쇄된다.
요 부분은 자료가 많긴한데, 오늘의 메인 요리는 아니라서 일단 공식 자료만 남겨두겠다.
서론이 길었는데, 진짜 시작한다. 아무래도 기술 또한 영업이라 "왜"라는 질문에 답하는 것이 중요해서 길어졌다. 또 길어지려고 하니 조용.
그렇다면 타입의 종류에는 어떤 것들이 있을까?
이 글은 타입스크립트의 기초를 모두 다루는 것이 아니기에, 타입스크립트 기본 타입을 읽고 오면 이해에 큰 도움이 될 것이다. 이어가면, JSDoc에서는 타입스크립트의 타입정의를 /** @type {타입정의} */
괄호안에 넣어주면 끝난다.
백문이 불여일견! 예시로 이해해보자.
string
이다/** @type {string} */
let 이름 = "나 string임"
any
/** @type {?} */
let 드루와 = () => () => () => "이게 되네"
// 혹은 * 도 any타입으로 사용가능하다
/** @type {*} */
let 이걸또드루와 = () => () => () => 드루와()()()
number
의 배열이다. 그냥 배열인데 배열 요소가 숫자
라는 간단한 이야기/** @type {number[]} */
const 나이모음 = [20, 21, 22]
div
에 대해 접근 가능한 property 목록을 강제하고 싶을 때 HTMLDivElement
타입을 사용하면 된다.// DOM 프로퍼티를 사용하여 HTML 요소를 지정할 수 있습니다
/** @type {HTMLDivElement} */
const DIV태그 = document.querySelector("div");
/**
* @type {boolean | string}
*/
const 오늘은_솔로_탈출가능합니까_휴먼? = Math.random() > 0.2 ? "역시 born to be 개발자" : true;
오_솔_탈
변수를 보면, 그 값이 boolean
(=true) 혹은 string
(...)이 올 것을 사전에 정의할 수 있다.
/** @type {{ 이름: string, 성별: "남자" | "여자" }} */
const 정보 = { 이름: "danpacho", 성별: "남자" }
여기서 좀 독특한 타입이 등장하는데, 바로
literal
타입이다.
성별: "남자" | "여자"
이렇게 정의를 하면, 성별로는 다른string
을 절대 넣을 수 없다.다른 언어와 달리 타입스크립트가 가진 강력한 장점이라 할 수 있다.
참고로 이 literal 타입은 굉장한 잠재력을 갖고 있다.
/** @type {(members: {name: string, age: number}[]) => {name: string, age: number}[]} */
const 미성년자는_걸러버려 = (members) => members.filter(({ age }) => age > 19)
/** @type {Function} */
const 바보입니다 = () => {}
함수는 다양한 방식으로 정의할 수 있다.
바보입니다
처럼, Funtion
이라고 함수를 뭉등그려서 타입스크립트에게 이야기 하기즉 미성년자를 걸러버리는
함수처럼 어떤 인자를 받아 어떤 결과를 만들어 내는지 구체적으로 정의할 수 있다.
타입스크립트를 사용하면 두번째 방법을 이용하게 될 것이다.
그런데 혹시 주석 보면서 조금 불편함을 느낀 사람 없는가? {name: string, age: number}[]
이 글자가 반복 되고 있다.
중복을 없애러 가보자.
이러한 중복을 해결해주는 친구가 바로 타입 정의, 즉 @typedef
라는 키워드다. 중복되는 혹은 핵심적으로 사용되는 타입은 @typedef
로 따로 변수처럼 정의할 수 있다.
예시를 함께 보자.
object
정의 방법은 총 2가지가 있다.
다소 복잡한 @property
혹은 @prop
이라는 키워드를 사용하는 방식이다.
/**
* @typedef {Object} 동물 동물 객체 타입입니다
* @property {string} 이름 동물의 이름입니다
* @property {number} 수명 동물의 수명입니다
* @property {string=} 종류 동물의 종류입니다
* @prop {number} [호감도] 개인적 호감도입니다
* @prop {number} [지력=10] 동물의 지력입니다
*/
/**@type {동물} */
const 해달 = {
이름: "보노보노",
수명: 25,
종류: "해달",
호감도: Infinity,
}
위와 같은 방식으로 동물
타입을 정의하면 vscode에서 아래와 같이 설명과 타입이 정의된다. 각 property의 설명과 타입이 있는 것은 굉장히 유용하니 계속 읽어보도록 하자.
{타입=}
혹은 [타입이름]
으로 해당 object
속성이 선택적임을 명시할 수 있다. 예컨데 종류
와 호감도
를 선택적으로 표기하고 싶을때 이렇게 적으면 된다./**
* @property {string=} 종류 - 동물의 종류입니다
* @prop {number} [호감도] - 개인적 호감도입니다
*/
[property이름=기본값]
으로 객체의 기본 값을 지정할 수 있다. 그러나 기본값의 경우 함수의 인자의 기본값을 지정할 때만 유의미하다는 것을 명심해야된다./**
* @prop {number} [지력=10] - 동물의 지력입니다
*/
/**
* @prop {number?} 지력 - 동물의 지력입니다
*/
{타입?}
로 null
이 될 수 있음을 명시한다. 그러나 해당 기호는 jsconfig.json
의 strictNullCheck
가 활성화 중인 경우에만 의미가 있다./**
* @type {?number}
* With strictNullChecks: true -- number | null
* With strictNullChecks: false -- number
*/
let nullable = null;
두번째는 @typedef
키워드와 타입스크립트 구문을 활용한 방식이다.
사용방법: @typdef{ts타입}
/**
* @typedef {{이름: string, 수명: number, 종류?: string, 호감도?: number, 지력?: number}} 동물2
*/
/**@type {동물2} */
const 해달2 = {
이름: "보노보노",
수명: 25,
종류: "해달",
호감도: Infinity,
}
위에서 적은 {이름: string, 수명: number, 종류?: string, 호감도?: number, 지력?: number}
은 타입스크립트의 타입 정의 방식이니, 지금은 크게 신경쓰지 않아도 좋다. 그러나 타입스크립트를 아는 경우에 이 방식을 사용하는 것이 훨씬 유용할 것이다.
함수의 정의도 Object와 마찬가지로 2가지 방법이 존재한다.
첫번째로 @callback
키워드를 활용한 방식이 있다.
이제 obejct 타입 정의하는 방법도 알아 보았으니, 아까 미성년자 걸러버리는 함수를 리팩토링 해보자.
// 먼저 반복되는 Person 타입을 추출하자
/**@typedef {{age: number, name: string}} Person */
/**
* @callback 걸립니다
* @param {Person[]}
* @returns {Person[]}
*/
/** @type {걸립니다} */
const 미성년자는_걸러버려 = (people) => people.filter(({ age }) => age > 19)
전의 중복을 Member라는 새로운 타입선언과 함께 없앤 모습이다.
또한 @param
역시 @property
혹은 @prop
과 마찬가지로 기본값이 있는 경우, 선택적인 경우 및 nullish
한 경우를 동일하게 적용할 수 있다.
특히 @returns
는 함수의 반환값을 명시하는 키워드로, 오직 함수에서만 사용가능하다.
때로 타입스크립트 파일과 연동하여 외부 파일에서 타입을 불러오고 싶은 경우가 있을 수 있습니다. 이때 필요한 것이 타입 import 기능이다.
타입은 파일의 import 경로를 통해 가져올 수 있다.
예제를 함께 확인하자.
타입스크립트의 타입선언 파일 동물.d.ts에서 type 동물을 가지고 와보자.
여기서 잠깐,
**.d.ts
파일은, definition.typescript의 축약어로 타입스크립트에서 오직 타입 정의만을 사용하고 싶을때(= 실제 js 로직이 전혀 포함이 되지 않을때) 사용하는 파일이다. 사실상 타입만 모인 고인물 공간이라 보면된다.
동물.d.ts
외부 파일, 이는 타입스크립트 파일이다. 그러나 자바스크립트 또한 동일한 방식으로 사용가능하니 계속해서 보자.
export type 동물 = {
이름: string
수명: number
}
index.js
/** @type { import("./types/동물").동물 } */
const 동물 = {
이름: "캥거루"
수명: 12
}
여기서 눈여겨 볼 점은 import
경로에 .d.ts
, 즉 파일 확장자가 포함되지 않았다는 점이다.
그러나
jsconfig.json
에서moduleResolution
이node
로 설정된 경우에는 확장자를 반드시 명시해야 한다.
또한 불러온 타입을 @typedef
키워드를 이용하면, 한개의 타입으로 추출할 수 있다.
/** @typedef { import("./types/동물").동물 } 외부동물타입 */
/** @type {외부동물타입} */
const 동물 = {
이름: "캥거루",
수명: 12
}
이제 기본적인 타입 정의 방법은 모두 익혔다. 사실 이정도로 수준으로 JSDoc을 활용할 수 있다면, 당신은 이미 타입스크립트를 사용할 수 있다:)
그러나 타입시스템이 번거로울 때가 종종 있다. 예컨데 복잡한 함수의 인자로 분명히 이 타입이 들어가는 것을 개발자는 알지만, 타입스크립트가 보기에는 아닐 수도 있는 경우가 있기 때문이다.(물론 스크립트 형님을 믿는게 대부분-95% 맞다). 이런 경우는 직접 분기처리 혹은 타입 오류를 없애기 위해 노력하기 보다는 "얘는 이 타입이다!" 라고 말해주는것도 방법이 될 수 있다.
그럼 type assertion은 어떻게 하는 걸까?
이해를 돕기위해 솔로 탈출 예제를 다시 돌아보자.
/**
* @type {() => boolean | string}
*/
const 탈출머신 = () => Math.random() > 0.2 ? "역시 born to be 개발자" : true;
먼저 탈출 예제를 응용해 탈출머신이라는 랜덤 함수를 만들어 보자. 위 함수는 대략 20%의 확률로 긍정적 결과를 얻을 수 있다. 이제 실행해보자.
const 탈출_아마도_트루 = 탈출머신()
탈출_아마도_트루
는 우리가 정의한대로 boolean
혹은 string
이 나온다고 예측이 될 것입니다.
그러나 나는 탈출할 것이라는 강력한 확신이 있다. 탈출을 감행해보자.
const 탈출_참트루 = /** @type {boolean} */ 탈출머신()
이제 탈출_참트루
변수는 true
, 즉 boolean
타입으로 고정되었다.
이처럼 괄호로 묶인 표현식 앞에 @type
태그를 추가하여 다른 유형으로 assertion 할 수 있다. 꽤나 유용하지만, 자주 사용하면 타입스크립트를 사용하는 의미가 퇘색될 수 있으니 적절하게 사용하자.
아 한가지 까먹고 작성을 덜 했는데, 바로 고급 주석 적기 기술이다.
이에 대한 몇가지 팁이 있는데 다음과 같다.
어떤 함수, object key, 혹은 변수에 JSDoc의 @description
키워드를 사용하면 명시적으로 해당 기능을 적어줄 수 있다.
/**
* @description 애플 주식은 오를것
*/
const apple = "애플 주식을 사라 아들아..."
/**
* @description 인사 각도로 인사성을 체크하는 함수
*/
const 인사성_체크 = (degree) => degree > 90 ? "참 인성이 올바르구나" : "인성이 부족하군요"
/**
* @description 싸움구경 오세요
*/
const fighter = {
/**
* @description 화성인
*/
"일론머스크": "내가 이겨",
/**
* @description 외계인
*/
"meta ceo": "제가 이깁니다."
}
@see를 사용해서 링크 참조를 알려주고, @link로 외부 링크로 연결시키면 된다.
/**
* @see {@link https://자폭.com 자폭하시겠습니까?}
*/
const 누르면_폭파 = "https://자폭.com"
@example 키워드를 사용하면 코드 예제를 주석에 추가할 수 있다.
/**
* @description 인사 각도로 인사성을 체크하는 함수
* @example
* ```js
* // 예의가 없는 인사각도 89 사람
* const 인성미달 = 인사성_체크(89)
*
* // 예의가 바른 인사각도 91 이상의 사람
* const 인성합격 = 인사성_체크(91)
* ```
*/
const 인사성_체크 = (degree) => degree > 90 ? "참 인성이 올바르구나" : "인성이 부족하군요"
이제 사용자는 아주 직관적으로 함수의 사용 방법을 이해할 수 있다.
위 3가지는 미래 자신 + 오픈소스 기여에 큰 도움을 주는 꿀팁이기 때문에 잘 챙겨가면 좋을 것 같다.
지금까지 공부한 내용을 쭉 돌아보자.
타입의 선언방법 그리고 타입의 종류 및 타입 정의 방법을 통해 JSDoc의 기본적인 타입 시스템을 알아 보았다. 추가적으로 타입을 외부에서 가져오는 방법, 그리고 타입의 강제 지정 방법 등의 유용한 방법까지 터득할 수 있었다.
여기서는 살펴보지 않았지만, @template
를 활용한 제네릭 타입 지정법과 클래스의 @constructor
, @extends
, @this
키워드 활용법 그리고 @enum
을 활용한 타입스크립트 ENUM
등 추가적인 고급 주제들이 있다.
해당 주제는 지금까지 살펴본 기본적인 JSDoc 타입과 타입스크립트의 문법을 이해한 다음에, 필요한 경우 스스로 찾고 학습하는 것이 더욱 유용할 것이다.
이 글은 공식문서와 나의 사용 경험을 기반으로 작성해본 입문 가이드라고 생각하면 좋을 것 같다.
JSDoc을 통한 점진적인 타입 시스템 도입으로 더욱 견고해진 코드를 만나보면서, 타입스크립트까지 자연스럽게 입문하는 발판이 되었으면 한다.
와 좋은글 진짜 감사합니다! UI 라이브러리를 구축하려고 하니, param이 많아 인자의 type체크가 번거로워지더라고요. 현재 프로젝트가 자바스크립트로 진행되어 타입스크립트가 사용이 안돼서 JSDoc으로 처리하고 싶었는데, 정말 도움 많이 됐습니다!