출처: copilot으로 제작

 

지난번에 JSDoc으로 더 나은 주석 작성하기, 그리고 개발 관련 도서 추천이라는 글을 썻었는데요. JSDoc으로 함수, 클래스, 변수에 설명을 추가할 뿐만 아니라 타입을 지정해 주어 쉽게 파악이 가능한 주석을 작성하는 법을 포스팅했습니다. 오늘은 이 JSDoc을 이용하여 ts 파일이 아닌 js 파일에서도 타입을 검사할 수 있게 만드는 법을 포스팅하겠습니다. 더불어 타입스크립트처럼 사용할 수 있도록 제넥릭과 타입 캐스팅 같이 조금 더 심화된 사용법을 설명해드리겠습니다.

JS 파일에서 타입검사하기

// @ts-check

파일의 최상단에 위와 같은 키워드를 붙이면 js 파일에서도 타입스크립트와 같은 타입검사가 가능합니다. 이 키워드를 사용한다면 JavaScript 파일에 타입 정보와 주석을 추가하여 코드의 타입 오류를 발견하고, IDE 또는 에디터에서 타입 검사를 수행할 수 있도록 합니다. 그리고 이 기능은 JSDoc을 사용할 때 타입스크립트 못지 않은 타입 검사를 할 수 있습니다. 물론 타입스크립트의 타입 검사기를 사용하기에 JSDoc에서는 사용이 어려운 타입스크립트의 고급 기능 등 더 정확한 검사를 위해서는 타입스크립트 설치와 ts 파일에서 사용하는 것이 나을 수도 있습니다.

제네릭

제네릭은 코드의 재사용성을 높이고 다양한 타입을 지원할 수 있도록 해주는 중요한 도구입니다. JSDoc에서도 제네릭 타입을 정의하고 사용할 수 있으며, Object 등 기본적인 제네릭을 제공합니다.

제네릭 타입을 만들어서 사용하기

JSDoc에서는 제네릭 함수를 정의할 때, @template 태그를 사용하여 제네릭 타입 매개변수를 지정할 수 있습니다.

/**
 * 주어진 타입을 그대로 반환하는 제네릭
 * @template T
 * @typedef {Object} Container
 * @property {T} value
 */

다음 코드를 살펴보겠습니다.

/**
 * 두 값을 더하는 함수에 사용할 제네릭 콜백 타입
 * string 또는 number 타입만 허용
 * @template T
 * @callback AddGeneric
 * @param {T} value1 - 더할 값 1
 * @param {T} value2 - 더할 값 2
 * @returns {T} - 더한 결과
 */

 

먼저 처음 보는 @callback 태그는 @typedef와 비슷하지만 함수의 타입 지정울 뜻합니다. 따라서 @param은 객체의 속성이 아닌 함수의 매개변수(parameter)를 뜻합니다. 이 @callback 태그는 AddGeneric라는 이름으로 지정되어 있고 value1과 value2는 같은 속성이고 그 두 값을 더한 결과를 리턴합니다. 자바스크립트에서는 문자열 간에 덧셈 연산이 가능하니 제네릭 T는 string과 number만 들어올 수 있게 해야합니다. 따라서 string | number@template 키워드에 타입 지정해주었습니다.

@template과 @callback 태그를 함께 사용할 때는 @template이 @callback보다 위에 있거나 @callback의 return 이후에 있어야합니다.

 

사실 @callback의 함수 타입 지정은 @typeof에서도 사용할 수 있습니다. 훨씬 짧은 코드로 같은 기능을 수행할 수 있습니다.

/**
 * 두 값을 더하는 함수에 사용할 제네릭 타입
 * @template {string | number} T
 * @typedef {(value3: T, value4: T) => T} AddGeneric2
 */

이렇게 만들어진 함수 타입은 다음과 같이 사용할 수 있습니다.

/**
 * 두 숫자를 더하는 함수
 * @type {AddGeneric<number>}
 */
function add(value1, value2) {
  return value1 + value2;
}

이제 이 함수의 타입은 숫자를 더하는 함수가 되었습니다. 만약 AddGeneric<number> 대신AddGeneric`을 사용한다면 문자열을 더하는 함수가 됩니다.

함수에 바로 사용하기

@typedef@callback을 사용하지 않고 함수나 클래스를 선언하면서 바로 제네릭을 사용하여도 아무 문제가 없습니다.

  /**
 * @template  T
 * @param {T} value1
 * @param {T} value2
 */
function add2(value1, value2) {
  if (typeof value1 === "string" && typeof value2 === "string") {
    return value1 + value2;
  } else if (typeof value1 === "number" && typeof value2 === "number") {
    return value1 + value2;
  }

  throw new Error("더할 수 없는 요소");
}

타입 단언(Type Assertion)

타입스크립트를 사용하다가 JSDoc에 익숙해지기 위해 일부러 JSDoc의 기능만을 이용하여 타입을 검사할 때, 다음과 같은 문제점에 부딪혔습니다.

// @ts-check

let a = [1, 2, "3", "4"];
const numSum = a[0] + a[1];

 

분명 a의 0번 인덱스와 1번 인덱스는 모두 숫자인데 두 수의 합을 구하려고 하면 오류가 나게 됩니다. 타입스크립트였다면 as 키워드를 통해 타입 단언을 하였겠지만, JSDoc을 js 파일에서 사용한다면 주석 바깥의 부분은 일반 자바스크립트와 다를 바없습니다. 그래서 as!와 같은 간편한 타입단언을 지원하지 않습니다. 대신 조금 더 번거로운 방법의 타입 단언을 사용해야 합니다.

const numSum = /** @type {number} */ (a[0]) + /** @type {number} */ (a[1]);

 

변수를 처음 만들 때와 같은 방식으로 @type 태그를 사용하여 타입 단언을 할 수 있습니다. 이때 주의해야 할 점은 단언할 타입을 꼭 소괄호로 묶어주어야 한다는 것입니다. 그렇지 않으면 타입 단언이 이루어지지 않습니다.

몇 가지 주의할 점

objectObject{}을 타입으로 지정할 때 객체만을 뜻하지는 않습니다.

// @ts-check

/** @type {object} */
let object;
object = {"d": 2};
object = "string";
object = 1;

 

위의 코드는 // @ts-check를 이용하였음에도 어떠한 오류도 발생하지 않습니다. 확인해보면 object 변수는 any로 지정되어 사용시 주의가 필요합니다. 만약 객체인 타입이 필요하다면 객체 키-값이 정의된 타입을 만들거나 키와 값의 타입을 알수 없다면 대안으로 { [key: string]: any }와 같이 사용하는 것이 좋습니다.

+ Recent posts