고품격 자바스크립트 - JSDoc

해달 보호협회·2023년 8월 11일
1

고품격 JS

목록 보기
1/1
post-thumbnail

JSDoc 공식문서

본 글은 타입스크립트 JSDoc 문서에 기반해서 작성되었다.

typescript - JSDoc

JSDoc을 왜 써야하지?

요즘 확실히 타입스크립트로 고통 받는 개발자의 함성을 자주 듣고 있다. 이미 js의 유연함에 만족하시는 사람은 ts의 필요성을 느끼지 못하기 마련이기에 학습할 필요성을 느끼지 못하기 마련이다. 나 또한 사용하기 전에 그랬다.

또한 갑자기 js에서 ts로 갈아타기는 사실 별거 아니라고 하기에는 타입스크립트 문법과 환경 셋팅법 등 꽤나 학습 비용이 있다. 그리고 초심자를 위해 잘 정리한 자료를 찾는 것도 고된 일이다.

그러나 놀랍게도 js를 사용하면, ts는 자연스럽게 따라온다. 바로 JSDoc, 타입스크립트의 타입 시스템을 자바스크립트의 “주석”만으로 살짜쿵 얹어버리는 마법이다.

이미 친숙한 자바스크립트에 주석만으로 ts 체험을 할 수 있다는 사실만으로 타입스크립트에 대한 장벽을 낮추고 있다.(jsconfig.json 파일 설정을 제외하면)

그리고 가장 중요한 점을 말하지 않았다! 조금씩 타입 "주석"을 적다보면 타입스크립트에 대한 욕구와 자신감이 상승한 자신을 발견할 수 있다! 이는 개발자 dna에 내재되어 있는 강타입에 대한 욕구를 촉진시킬 것이다.

JSDoc 시작 이전에 타입스크립트 기본 타입을 숙지하고 오면 더욱 큰 도움이 된다.

그렇다면 JSDoc은 언제 사용하면 좋을까?

  1. 타입스크립트 환경 셋업은 귀찮고 효과만 체험하고 싶은 사람
  2. 이미 js 프로젝트로 시작했고, 타입스크립트를 도입하기에는 너무 버거운 상황
  3. 점진적인 타입스크립트 도입

혹은 가벼운 프로젝트를 시작하는데 굳이 타입스크립트를 도입하는것이 귀찮다는 생각이 들 때, 주석을 작성하는 것만으로도 타입을 적용할 수 있다는 강력한 장점이 있다.

나의 경우에는 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()

image

JSDoc의 단점

그렇다면 JSDoc은 단점이 없을까?

당연히 있다.

무엇보다 "주석"으로 모든 것을 해결하기 때문에 타입스크립트의 일부 기능들을 사용하는것이 제한적이며, 보다 장황해진다는 단점이 있다. 또한 타입을 불러오는 것 또한 import("타입 경로")라는 구린 문법을 사용해야 되므로, 아쉽기도 하다.

그렇지만 좋은걸?

그러나 개인적 사용경험에 따르면 패키지 설치 + 설정 이슈 없이 순수 타입 시스템을 도입할 수 있다는 것은 정말 큰 장점이라는 생각이 들었다. 또한 vscode 에 /** 만 입력해도 함수에 대한 jsdoc을 자동 생성해주는 기능이 있기에 불편한 점이 꽤나 상쇄된다.

jsconfig.json

요 부분은 자료가 많긴한데, 오늘의 메인 요리는 아니라서 일단 공식 자료만 남겨두겠다.

jsconfig.json Reference

진짜 JSDoc 알아보기

서론이 길었는데, 진짜 시작한다. 아무래도 기술 또한 영업이라 "왜"라는 질문에 답하는 것이 중요해서 길어졌다. 또 길어지려고 하니 조용.

1. 타입의 종류

그렇다면 타입의 종류에는 어떤 것들이 있을까?

이 글은 타입스크립트의 기초를 모두 다루는 것이 아니기에, 타입스크립트 기본 타입을 읽고 오면 이해에 큰 도움이 될 것이다. 이어가면, JSDoc에서는 타입스크립트의 타입정의를 /** @type {타입정의} */ 괄호안에 넣어주면 끝난다.

백문이 불여일견! 예시로 이해해보자.

  • String: 이름은 string 이다
/** @type {string} */
let 이름 = "나 string임"
  • Any: 아무거나 드루와 타입, any
/** @type {?} */
let 드루와 = () => () => () => "이게 되네"

// 혹은 * 도 any타입으로 사용가능하다
/** @type {*} */
let 이걸또드루와 = () => () => () => 드루와()()()
  • Array: 나이 모음은 number의 배열이다. 그냥 배열인데 배열 요소가 숫자라는 간단한 이야기
/** @type {number[]} */
const 나이모음 = [20, 21, 22]
  • DOM: 심지어 DOM도 타입을 지정할 수 있다. 예컨데 div에 대해 접근 가능한 property 목록을 강제하고 싶을 때 HTMLDivElement타입을 사용하면 된다.
// DOM 프로퍼티를 사용하여 HTML 요소를 지정할 수 있습니다
/** @type {HTMLDivElement} */
const DIV태그 = document.querySelector("div");
  • Union: 타입을 몇가지 동시에 사용하자
/**
 * @type {boolean | string}
 */
const 오늘은_솔로_탈출가능합니까_휴먼? = Math.random() > 0.2 ? "역시 born to be 개발자" : true;

오_솔_탈 변수를 보면, 그 값이 boolean(=true) 혹은 string(...)이 올 것을 사전에 정의할 수 있다.

  • Object: 자바스크립트의 object의 각 property, value 구조 정의
/** @type {{ 이름: string, 성별: "남자" | "여자" }} */
const 정보 = { 이름: "danpacho", 성별: "남자" }

여기서 좀 독특한 타입이 등장하는데, 바로 literal 타입이다.
성별: "남자" | "여자" 이렇게 정의를 하면, 성별로는 다른 string을 절대 넣을 수 없다.

다른 언어와 달리 타입스크립트가 가진 강력한 장점이라 할 수 있다.
참고로 이 literal 타입은 굉장한 잠재력을 갖고 있다.

  • Funcion: 함수의 인자, return value에 대한 타입 지정
/** @type {(members: {name: string, age: number}[]) => {name: string, age: number}[]} */
const 미성년자는_걸러버려 = (members) => members.filter(({ age }) => age > 19)

/** @type {Function} */
const 바보입니다 = () => {}

함수는 다양한 방식으로 정의할 수 있다.

  1. 바보입니다 처럼, Funtion이라고 함수를 뭉등그려서 타입스크립트에게 이야기 하기
  2. 인자, reutrn 값에 대해 명확하게 명시하기

미성년자를 걸러버리는 함수처럼 어떤 인자를 받아 어떤 결과를 만들어 내는지 구체적으로 정의할 수 있다.

타입스크립트를 사용하면 두번째 방법을 이용하게 될 것이다.

그런데 혹시 주석 보면서 조금 불편함을 느낀 사람 없는가? {name: string, age: number}[] 이 글자가 반복 되고 있다.

중복을 없애러 가보자.

2. 타입 정의

이러한 중복을 해결해주는 친구가 바로 타입 정의, 즉 @typedef 라는 키워드다. 중복되는 혹은 핵심적으로 사용되는 타입은 @typedef 로 따로 변수처럼 정의할 수 있다.

예시를 함께 보자.

A. Object 정의

object 정의 방법은 총 2가지가 있다.

A-1. @property, @prop

다소 복잡한 @property 혹은 @prop 이라는 키워드를 사용하는 방식이다.

/**
 * @typedef {Object} 동물 동물 객체 타입입니다
 * @property {string} 이름 동물의 이름입니다
 * @property {number} 수명 동물의 수명입니다
 * @property {string=} 종류 동물의 종류입니다
 * @prop {number} [호감도] 개인적 호감도입니다
 * @prop {number} [지력=10] 동물의 지력입니다
 */

/**@type {동물} */
const 해달 = {
    이름: "보노보노",
    수명: 25,
    종류: "해달",
    호감도: Infinity,
}

위와 같은 방식으로 동물 타입을 정의하면 vscode에서 아래와 같이 설명과 타입이 정의된다. 각 property의 설명과 타입이 있는 것은 굉장히 유용하니 계속 읽어보도록 하자.

jsdoc 예제

  • 선택적 객체 프로퍼티 {타입=} 혹은 [타입이름]으로 해당 object 속성이 선택적임을 명시할 수 있다. 예컨데 종류호감도를 선택적으로 표기하고 싶을때 이렇게 적으면 된다.
    /**
    * @property {string=} 종류 - 동물의 종류입니다
    * @prop {number} [호감도] - 개인적 호감도입니다
    */
  • 기본값이 있는 객체 프로퍼티 [property이름=기본값]으로 객체의 기본 값을 지정할 수 있다. 그러나 기본값의 경우 함수의 인자의 기본값을 지정할 때만 유의미하다는 것을 명심해야된다.
    /**
    * @prop {number} [지력=10] - 동물의 지력입니다
    */
  • nullish 객체 프로퍼티
    /**
    * @prop {number?} 지력 - 동물의 지력입니다
    */
    {타입?}null이 될 수 있음을 명시한다. 그러나 해당 기호는 jsconfig.jsonstrictNullCheck가 활성화 중인 경우에만 의미가 있다.
    /**
     * @type {?number}
     * With strictNullChecks: true  -- number | null
     * With strictNullChecks: false -- number
     */
    let nullable = null;

A-2. @typedef 키워드 + 타입스크립트 구문

두번째는 @typedef 키워드와 타입스크립트 구문을 활용한 방식이다.

사용방법: @typdef{ts타입}

/**
 * @typedef {{이름: string, 수명: number, 종류?: string, 호감도?: number, 지력?: number}} 동물2
 */

/**@type {동물2} */
const 해달2 = {
    이름: "보노보노",
    수명: 25,
    종류: "해달",
    호감도: Infinity,
}

위에서 적은 {이름: string, 수명: number, 종류?: string, 호감도?: number, 지력?: number}은 타입스크립트의 타입 정의 방식이니, 지금은 크게 신경쓰지 않아도 좋다. 그러나 타입스크립트를 아는 경우에 이 방식을 사용하는 것이 훨씬 유용할 것이다.

B. 함수의 정의

함수의 정의도 Object와 마찬가지로 2가지 방법이 존재한다.

B-1. @callback

첫번째로 @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함수의 반환값을 명시하는 키워드로, 오직 함수에서만 사용가능하다.

C. 타입 가져와, import

때로 타입스크립트 파일과 연동하여 외부 파일에서 타입을 불러오고 싶은 경우가 있을 수 있습니다. 이때 필요한 것이 타입 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에서 moduleResolutionnode로 설정된 경우에는 확장자를 반드시 명시해야 한다.

또한 불러온 타입을 @typedef 키워드를 이용하면, 한개의 타입으로 추출할 수 있다.

/** @typedef { import("./types/동물").동물 } 외부동물타입 */

/** @type {외부동물타입} */
const 동물 = {
	이름: "캥거루",
	수명: 12
}

이제 기본적인 타입 정의 방법은 모두 익혔다. 사실 이정도로 수준으로 JSDoc을 활용할 수 있다면, 당신은 이미 타입스크립트를 사용할 수 있다:)

3. 타입 이거다! Assertion

그러나 타입시스템이 번거로울 때가 종종 있다. 예컨데 복잡한 함수의 인자로 분명히 이 타입이 들어가는 것을 개발자는 알지만, 타입스크립트가 보기에는 아닐 수도 있는 경우가 있기 때문이다.(물론 스크립트 형님을 믿는게 대부분-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 할 수 있다. 꽤나 유용하지만, 자주 사용하면 타입스크립트를 사용하는 의미가 퇘색될 수 있으니 적절하게 사용하자.

고급 주석 스킬

아 한가지 까먹고 작성을 덜 했는데, 바로 고급 주석 적기 기술이다.

이에 대한 몇가지 팁이 있는데 다음과 같다.

1. @description 설명 키워드

어떤 함수, object key, 혹은 변수에 JSDoc의 @description 키워드를 사용하면 명시적으로 해당 기능을 적어줄 수 있다.

/**
 * @description 애플 주식은 오를것
 */
const apple = "애플 주식을 사라 아들아..."

/**
 * @description 인사 각도로 인사성을 체크하는 함수
 */
const 인사성_체크 = (degree) => degree > 90 ? "참 인성이 올바르구나" : "인성이 부족하군요"


/**
 * @description 싸움구경 오세요
 */
const fighter = {
    /**
     * @description 화성인
    */
    "일론머스크": "내가 이겨",
    /**
     * @description 외계인
    */
    "meta ceo": "제가 이깁니다."
}

@description 키워드

2. @link와 @see

@see를 사용해서 링크 참조를 알려주고, @link로 외부 링크로 연결시키면 된다.

/**
 * @see {@link https://자폭.com 자폭하시겠습니까?}
 */
const 누르면_폭파 = "https://자폭.com"

자폭예제

3. @example

@example 키워드를 사용하면 코드 예제를 주석에 추가할 수 있다.

/**
 * @description 인사 각도로 인사성을 체크하는 함수
 * @example
 * ```js
 * // 예의가 없는 인사각도 89 사람
 * const 인성미달 = 인사성_체크(89)
 * 
 * // 예의가 바른 인사각도 91 이상의 사람
 * const 인성합격 = 인사성_체크(91)
 * ```
 */
const 인사성_체크 = (degree) => degree > 90 ? "참 인성이 올바르구나" : "인성이 부족하군요"

이제 사용자는 아주 직관적으로 함수의 사용 방법을 이해할 수 있다.

@example 키워드


위 3가지는 미래 자신 + 오픈소스 기여에 큰 도움을 주는 꿀팁이기 때문에 잘 챙겨가면 좋을 것 같다.

마지막으로

지금까지 공부한 내용을 쭉 돌아보자.

타입의 선언방법 그리고 타입의 종류 및 타입 정의 방법을 통해 JSDoc의 기본적인 타입 시스템을 알아 보았다. 추가적으로 타입을 외부에서 가져오는 방법, 그리고 타입의 강제 지정 방법 등의 유용한 방법까지 터득할 수 있었다.

여기서는 살펴보지 않았지만, @template를 활용한 제네릭 타입 지정법과 클래스의 @constructor, @extends, @this 키워드 활용법 그리고 @enum을 활용한 타입스크립트 ENUM 등 추가적인 고급 주제들이 있다.
해당 주제는 지금까지 살펴본 기본적인 JSDoc 타입과 타입스크립트의 문법을 이해한 다음에, 필요한 경우 스스로 찾고 학습하는 것이 더욱 유용할 것이다.

이 글은 공식문서와 나의 사용 경험을 기반으로 작성해본 입문 가이드라고 생각하면 좋을 것 같다.

JSDoc을 통한 점진적인 타입 시스템 도입으로 더욱 견고해진 코드를 만나보면서, 타입스크립트까지 자연스럽게 입문하는 발판이 되었으면 한다.

profile
해달은 그저 둥둥 떠다니며, 손이 둥글기 때문에 당신에게 아무런 해를 끼칠 수 없습니다.

2개의 댓글

comment-user-thumbnail
2024년 1월 26일

와 좋은글 진짜 감사합니다! UI 라이브러리를 구축하려고 하니, param이 많아 인자의 type체크가 번거로워지더라고요. 현재 프로젝트가 자바스크립트로 진행되어 타입스크립트가 사용이 안돼서 JSDoc으로 처리하고 싶었는데, 정말 도움 많이 됐습니다!

1개의 답글