jsDoc은 자바스크립트 소스코드에 주석을 달기 위해 사용하는 마크업 언어이다. (위키백과)
jsDoc은 단순히 js 소스코드에 단순히 주석을 작성하는 것을 넘어, IDE에게 메소드의 매개변수, 리턴타입 힌트를 주어 자동완성 지원하도록 하는 것 까지 가능하다. 이를 통해 타입스크립트에서 인터페이스나 타입을 작성함을 통해 얻을 수 있는 타입지원과 유사한 경험을 할 수 있다.
장점: 주석은 물론이고 어느 정도의 타입지원을 받을 수 있다. 컴파일에 영향을 주지 않기 때문에 타입스크립트보다 번거롭지 않다.
단점: 타입스크립트 처럼 강력한 (컴파일 에러) 타입 체크를 할 수 는 없다.
자바나 타입스크립트와 같이 타입이 있는 언어를 사용해본 경험이 없다면, jsDoc에 타입을 지정하는 것조차 어려움을 느낄 수 있다.
추가의견: 타입힌트 지원 외에도 일반적인 주석 작성을 효율적으로 할 수 있도록 도와주므로, 어느 정도 수준까지는 어쩌피 익숙해져야 한다. (작성자 정보, 홈페이지 링크 연결 등 일반적으로 유용한 기능이 많음)
자바스크립트를 사용하는 React | Vue 프로젝트 진행 (타입스크립트 x)
jsDoc을 사용하고는 있었으나, 사용법이 정확하지 않아 주석 이상의 장점은 누리지 못하고 있었음.
주석을 작성하고자 하는 대상 바로 앞에서
/**
*
*/
function jsDoc() { // ... }
와 같이 주석을 작성하면 된다. (웹스톰에서는 /** 입력후 엔터키 입력하면 자동 완성)
이때 이미 함수의 매개변수를 정의해 둔 경우
/**
*
* @param number
* @param number2
*/
function jsdoc(number, number2) {
}
와 같이 자동으로 파라미터가 작성된다. 그럼 본격적으로 @param에 대해 파고들어보자
jsDoc의 @param은 함수의 매개변수에 대한 주석을 작성할 때 사용한다.
이때 jsDoc의 타입 지정을 통해 타입스크립트 처럼 매개변수의 타입을 부여할 수 있으며, object와 같은 복잡한 매개변수에 대해서도 타입 힌트를 주석으로 제공할 수 있다.
추가로 @return 태그를 사용하면 함수의 리턴타입 또한 지정할 수 있으며, 작성된 주석을 통해 IDE 사용시 타입스크립트의 힌트지원과 유사한 경험을 할 수 있다.
예시
/**
* add 함수
*
* @param {number} number 넘버1
* @param {number} number2 넘버2
*
* @returns {number}
*/
function add(number, number2) {
return number + number2
}
이렇게 작성된 jsDoc의 결과는 다음과 같다.
=> 타입스크립트를 사용하므로 인해 얻을 수 있는 타입힌트를 jsDoc에 타입을 알려주는 것만으로도 얻을 수 있다
변수 혹은 함수 뒤에 : 작성후 타입을 작성하면 해당 변수나 함수가 그 타입이라는 것을 뜻합니다. (함수의 경우 리턴타입)
const sum: number;
const isAdded: boolean;
const hello: string;
const numberArr: number[]
jsDoc을 좀더 공부해보게 된 이유가 된 부분입니다.
함수를 작성할 때 매개변수에 기본타입이 아닌 객체를 넘길 경우 jsDoc의 사용법
코드 예시는 상품 등록 api 호출을 가정하고 작성.
/**
* 상품 등록
* @param parameters 상품등록 파라미터
* @param { string } parameters.name 상품명
* @param { number } parameters.stock 상품 갯수
* @param { number } [parameters.size] 상품 사이즈
*
* @returns {*}
*/
function register(parameters) {
return axios.post('http://localhost:8080/product/', parameters)
}
위와 같이 파라미터가 객체인 경우 객체 속성 파라미터 주석은
@param { 타입 } 객체명.속성명 속성설명(optional)
과 같이 작성하면 됩니다. 이때 파라미터가 있어도 되고 없어도 되는 경우(optional)한 경우에는 [] 의 대괄호로 묶어주면 optional한 값이라고 나타낼 수 있습니다. 옵셔널 값의 경우 추후 타입 힌트시 변수 바로 뒤에 ? 표시가 나오는 것을 확인할 수 있습니다.
@param 태그를 통해 객체 파라미터의 속성을 정의해주는 방법 외에, 사전에 미리 사용자 정의 타입을 정의하여 사용하는 방법 또한 있습니다.
바로 @typeDef를 사용하는 방법인데 사용법은 다음과 같습니다.
/**
* @typeDef {정의할 타입의 타입(생략가능, 다른 타입을 적어주면 확장가능)} 정의할타입명 설명
*
* @property {해당타입의 속성의 타입(ex. number string 등)} 속성명 속성설명
*/
이 때 { } 안에 들어가는 속성으로는 'hi' 혹은 1, true 등과 같이 string, number 보다 더욱 상세히 표시할 수 있으며, | (or) 이나 & (and) 를 사용하는 것 또한 가능합니다.
(하단에서 관련하여 설명)
그럼 위와 같은 예시에 @typeDef를 적용한 예시를 보겠습니다.
/**
* @typedef {Object} productRegisterType 상품등록타입
* @property {string} name 상품명
* @property {number} stock 상품갯수
* @property {number} [size] 사이즈
*/
/**
*
* @param {productRegisterType} parameters
* @returns {*}
*/
function register2(parameters) {
return axios.post('http://localhost:8080/product/', parameters)
}
@param 을 통해 정의했을 때와 달리 사용자가 작성한 커스텀 타입임을 알려주고, 클릭시 properties를 확인할 수 있습니다.
이 방법의 장점은 타입을 사전에 작성해두고 여러곳에서 사용할 수 있다는 장점이 있습니다만, 미리 타입을 정의하는 주석 (@typeDef)을 작성해야 하는 번거로움이 있습니다.
따라서 개인적인 의견으로는 일회성으로 사용되는 함수의 매개변수의 경우는 @param을 통해 매개변수타입을 지정하고, 재사용될 경우가 있는 경우 혹은 함수의 리턴 타입으로 객체를 받는 경우 미리 해당 도메인의 속성을 정의해두는 용으로 사용하는 것이 효율적이라고 생각합니다.
예시
// api 리턴결과로 페이지 정보를 받는 경우 페이지의 타입 정의
/**
* @typedef page 페이지 정보
* @property { number } current 현재페이지
* @property { boolean } hasNext 다음 페이지 여부
* @property { boolean } hasPrev 이전 페이지 여부
* @property { number } size 페이지 사이즈
*/
// 위의 페이지 타입을 포함한 응답 객체를 정의 (응용)
/**
* @typedef BaseResponse
* @property { page } page 페이지정보
* @property { string } message 메세지
*/
// 상품도메인 타입 정의
/**
* 상품
*
* @typedef {Object} product 상품
* @property {string} name 상품명
* @property {number} stock 상품갯수
* @property {number} [size] 사이즈
* @property {string} img 이미지
*
*/
/**
* 상품 api 응답 타입 정의 ( typeDef 확장 )
* @typedef { BaseResponse & {data: product[]} } productsResponse
*/
// 상품목록 api 정의
/**
* 상품목록
*
* @param parameters 상품목록 조회 파라미터
*
* @returns {Promise<productsResponse>}
*/
async function productList(parameters) {
return axios.get('http://localhost:8080/products', { parameters })
}
위의 예시에서는 미리 서버 api 응답값 포멧에 대한 타입을 정의했고,
사전에 정의된 타입을 사용하고, 다른 타입에서 이 타입을 사용하고 확장하는 예시까지 포함했습니다.
추가로 상품도메인을 정의하고 이들을 조합하여 @return 부분에 타입으로 명시를 해주었습니다.
이렇듯 번거로운 작업을 통해 리턴타입을 지정해주는 이유는, 작성한 함수를 호출하여 데이터를 받아 해당 도메인에 접근할 때 빛을 발하기 때문입니다.
이렇게 무려 응답값과 데이터안에 들어있는 도메인의 속성을 알 수 있고, 도메인 속성의 타입까지 힌트를 받을 수 있게 됩니다.
타입스크립트를 배울 때 가장 편리하다고 느낀 부분이 타입을 미리 정의해둔 덕분에, 서버 응답값을 화면에서 사용하면서 해당 객체 필드를 자동완성으로 알 수 있는 부분이였는데요.
jsDoc에서도 타입을 미리 정의해둠으로서 이러한 편의를 얻을 수 있습니다.
string 타입 보다 더욱 특정한 문자열을 쓰는 특정한 상수 타입이 필요할 경우, 자바나 타입스크립트에서는 enum 타입을 활용합니다.
enum Location {
집: 'heaven',
회사: 'hell'
}
enum 타입을 활용할 경우 상수 변수들을 효율적으로 관리할 수 있고, (자바는 심지어 enum에 생성자나 메소드도 있고!) 일반적인 string, number 타입보다 엄격한 타입체크를 할 수 있는 장점이 있습니다만, 자바스크립트에서는 enum타입이 존재하지 않습니다.
따라서 자바스크립트에서는 다음과 같이 상수 객체를 사용하곤 합니다.
// 상수 객체라는 것을 표현하기 위한 관례로 대문자로 작성
const LOCATION = {
집: 'heaven',
회사: 'hell'
}
자바스크립트는 enum이 없으므로 상수 객체도 변경이 가능하고, 상수객체명을 대문자로 주고 const를 주더라도 변경이 가능하다는 한계가 있습니다. (조금더 변경을 막기위해 Object.freeze() 메소드를 활용하기도 합니다.)
하지만 jsDoc은 enum 타입을 가지고 있으며, 작성한 enum 타입을 jsDoc의 { } 에 적어줌으로서 타입 체크가 가능합니다. 주석에 불과하여 타입을 강제할 수는 없지만, 타입 힌트를 받을 시에 enum 이라는 것을 확인할 수 있는 것에는 의의가 있습니다. 작성시에 아무 string이나 number가 아닌 특정한 값을 줘야한다는 것을 확인할 수 있으니깐요.
/**
* @enum {{ 책: 'book', 문구: 'stationery', 기타: 'etc' }} PRODUCT_CATEGORIES
*/
const PRODUCT_CATEGORIES = {
책: 'book',
문구: 'stationery',
기타: 'etc'
}
사용 예시
/**
* 상품목록
*
* @param parameters 상품목록 조회 파라미터
* @param {PRODUCT_CATEGORIES} parameters.category 상품카테고리
*
* @returns {Promise<productsResponse>}
*/
async function productList(parameters) {
return axios.get('http://localhost:8080/products', { parameters })
}
@param의 {} 안에 작성한 enum 타입을 명시해줬습니다.
작성시 category가 어떤 타입이여야 하는지 타입힌트를 받을 수 있습니다.
아쉬운 점은 enum타입은 jsDoc에서만 존재하고 js에 있는 타입이 아니라, 타입 힌트 외의 역할을 해주지는 못하네요.
최근 타입스크립트 버전에
/** @import('./type.d.ts') */
이런 문법이 생겨서 d.ts에 타입정의하면서 사용할때 편하게 깔끔하게 쓸수있어요