logo

타입 확장과 지네릭

Chapter 2

58 조회

0 추천

1,667 단어

9분 예상

2024. 08. 28. 게시

luasenvy 작성

CC BY-NC-SA 4.0

타입 캐스팅

const variable: unknown = "hello";

console.info((<string>variable).length);
console.info((variable as string).length);

타입 캐스팅을 위한 문법이다. unknown 타입을 string 유형으로 변환하는 예제이다. 캐스팅 하지 않는다면 .length가 존재하지 않기 때문에 오류가 발생하지만 캐스팅을 통하여 컴파일러에게 해당 변수가 문자열임을 알려줄 수 있다.

const num: number = 0;
console.info(variable as string); // Error
console.info(variable as unknown as string);

타입 캐스팅은 만능이 아니고 호환되는 타입만 가능하다. 위처럼 숫자형과 문자형은 캐스팅이 불가능하기 때문에 오류가 발생한다. 그러나 강제로 하는 방법도 있는데 모든 자료형과 호환되는 unknown 타입으로 먼저 변환한 이후에 다시 원하는 타입으로 변환하는 것이다. 타입스크립트의 자료형에 대한 정확성이 낮아지고 코드가 지저분해지기 때문에 최대한 피해서 작성하자.

unknown, any, never, undefined, void

  • any: 타입스크립트의 검사를 생략
  • unknown: 어떤 값이 들어올지 알 수 없음. 모든 값이 허용됨
  • undefined: undefined
  • never: 실행되지 않음
  • void: 반환값을 고려하지 않음

각각의 타입들은 모두 비슷한 동작을 하는 것 같지만 실상은 그렇지 않다. 적재적소에 올바른 타입을 할당해야 컴파일과 런타임에서 오류를 발생시키지 않을 수 있다.

any vs unknown

any 타입은 타입스크립트의 검사를 생략하기 때문에 정말 특수한 경우가 아니라면 사용하지 않는 것이 좋다. 따라서 any 보다는 unknown을 사용하는 것이 좋다. 없는 문법이라고 생각하는 것이 프로그램의 오류를 줄이는 방법이다. 그리고 실제 변수가 사용되는 곳에 as 문법을 활용하여 타입 캐스팅을 하여 사용하는 것이 바람직하다.

never

const onlyThrow = (): never => {
  throw new Error("Error");
}

const neverEnd = (): never => {
  whild(true) console.info("loop.");
}

조금 생소한 유형인데 인터프리터가 도달실행하지 못하는 경우 사용한다. 위 예시처럼 함수가 종료되지 않는 경우 인터프리터가 함수 종료를 절대로 실행할 수 없기 때문에 반환형을 never 타입으로 설정할 수 있다.

void vs undefined

언뜻 보면 void와 undefined는 동일한 작동을 하는 것 처럼 보이고 undefined가 가독성을 떨어뜨려 별칭으로 void를 지원하는 것 아닌가 하는 기분이 든다. 그러나 작동방식에 약간 차이가 있다. 위에서 설명했듯이 void의 경우 반환형에 대해서 신경쓰지 않겠다는 이야기와 같다.

type CallbackType = (el: number) => void;

const arr = [1, 2, 3, 4, 5];
const callback: CallbackType = (el) => ++el;

arr.forEach(callback);

위 예시는 아주 간단한 forEach를 작성해본 것이다. 인자로 넘긴 함수는 배열을 순회하면서 요소들의 값을 1 증가시킨 후 반환하고 있다. forEach를 자주 써보면 알겠지만 이 인자로 넘기는 순회용 함수는 반환형이 뭐가 됐든 상관이 없다. Java나 C처럼 void가 아무런 값을 반환하지 않는다라는 의미와는 다르다.

const fnUndefined = (): undefined => 3; // Error
const fnVoid = (): void => 3;

const noReturn = (): undefined => {}
const noReturnRuntime = (): undefined => {
  return;
}

noReturn === noReturnRuntime // undefined === undefined -> true

조금 다른 예시를 들어보자. void의 경우 생뚱맞게 숫자를 반환했음에도 오류없이 동작한다. 반환된 값을 사용하지 말아라로 이해하는 것이 더 좋은 이해라고 생각한다. 자바스크립트는 함수가 종료될때까지 return 구문을 만나지 못하면 항상 undefined를 반환하게 되어있다. 함수가 종료되는 가장 마지막 줄에 return; 어렵다면 return undefined; 코드가 생략 되어있다고 생각하면 된다.

동적 필드 객체 타이핑

/* type */
type PlainObjectRecordType = {
  username: string;
} & Record<string, unknown>;

/* interface */
interface PlainObjectRecord {
  username: string;
  [key: string]: unknown;
}

위 예제의 두 타입은 동일하게 작동한다. 객체의 멤버로 username 키가 문자열로 반드시 있어야 하고 다른 필드는 어떤 것이 들어올지 모르겠으나 허용하는 타입이다. 객체를 다루기 시작하면 점점 타입 선언이 길어지고 복잡해지는데 [key: string]: unknown 보다는 Record<Key, Value> 방식이 조금 더 가독성이 좋아 더 선호된다. 그러나 피할 수 없는 케이스도 있을 수 있으므로 둘다 알아두어야 한다.

확장

type User = { name: string };
type ExtendUser = Param & { username: string }
type NextExtendUser = ExtendUser & { password: string }
interface User {
  name: string
}

interface ExtendUser extends {
  username: string
}

interface NextExtendUser extends {
  password: string
}

타입은 &를 사용하여 확장할 수 있고 인터페이스는 extends 문법을 활용하여 확장할 수 있다.

지네릭 ( Generic )

type ConditionalTypeParamRequired<T> = Array<T>;
type ConditionalTypeParamDefault<T = string> = Array<T>;
type ConditionalTypeParamExtends<T extends string> = Array<T>;

const generic: ConditionalTypeParamRequired<number> = [1, 2, 3, 4];
const defaultGeneric: ConditionalTypeParamDefault = ["A", "B", "C"];
const genericNotMatched: ConditionalTypeParamExtends<number> = [1, 2, 3, 4, 5]; // Error not 'string' type

지네릭이라는 생소한 단어 때문에 이해가 잘 안될 수도 있지만 타입 파라미터라고 생각하면 편하다. 그러나 많은 문서들이 지네릭이라고 지칭하기 때문에 단어는 꼭 기억해두어야한다.

지네릭을 사용하면 타입을 조금 더 동적으로 만들 수 있다. 지네릭을 통해 넘긴 타입형은 선언된 타입의 내부에서 모두 치환되어 동적으로 작동하게 된다. 지네릭은 기본값을 할당할 수도 있으며 extends문을 사용하면 넘겨받을 지네릭의 타입형을 제한할 수도 있다.

type ConditionalGenericParamAndResponse = <T = string>(param: T): Array<T>;

const getArray: ConditionalGenericParamAndResponse = (param) => [param];

const result = getArray(1); // Array<number>
const errors = getArray<boolean>(10); // Error

지네릭은 함수에도 적용할 수 있는데 함수 파라미터와 연결하여 사용하면 지네릭이 파라미터의 타입형으로 설정되어 가독성과 함께 타입스크립트가 코드와 더 잘 어울리도록 구성할 수도 있다.