[TypeScript] 제너릭, Type Predicate, 함수 오버로드, async/await

2026. 3. 31. 09:36·프론트엔드/Language

제너릭(Generic)

"함수에서 타입을 변수처럼 쓰는 것"이라고 정의하면 어렵게 느껴질 수 있다.

쉽게 말하면 이렇다. 보통 함수는 인자로 값을 받는다. 제너릭은 타입도 인자처럼 받을 수 있게 해 준다.

`any`를 쓰면 안 되는 이유는 명확하다.

`any`를 쓰는 순간 TypeScript가 타입 정보를 포기한다. 넣을 때도 `any`, 나올 때도` any`.

그냥 JavaScript 쓰는 것과 다를 게 없어진다.

제너릭을 쓰면 다르다.

function getFirst<T>(arr: T[]): T {
  return arr[0];
}

getFirst([1, 2, 3]);    // T가 number로 추론됨 → number 반환
getFirst(["a", "b"]);   // T가 string으로 추론됨 → string 반환

`T`는 "아직 정해지지 않은 타입"을 나타내는 자리다. 함수를 호출하는 순간 TypeScript가 인자를 보고 `T`가 뭔지 자동으로 추론해 준다. 넣은 타입이 그대로 나오는 것이다.

 

Constraint — "T가 최소한 이건 가져야 해"

 

제너릭은 기본적으로 어떤 타입이든 다 받을 수 있다. 그런데 때로는 "id가 있는 타입만 받고 싶어"처럼 조건을 걸고 싶을 때가 있다. 이럴 때 extends로 constraint를 건다.

function findById<T extends { id: number }>(items: T[], id: number): T | null {
  return items.find(item => item.id === id) ?? null;
}

`T extends { id: number }`는 "T는 반드시 `id: number`를 가지고 있어야 해"라는 조건이다.

이 조건을 만족하지 않는 타입은 아예 넣을 수 없다. 타입 수준에서 막아버리는 것이다.

예를 들어 이렇게 쓸 수 있다.

type User = { id: number; name: string };
type Product = { id: number; price: number };

findById([{ id: 1, name: "찰리" }], 1);       // User는 id: number 있음
findById([{ name: "찰리" }], 1);              // id 없어서 에러

 

Type Predicate

 

TypeScript는 함수 이름을 보지 않는다. 선언된 타입만 본다.

이게 무슨 말이냐면, 함수 이름이 `isAdmin`이어도 TypeScript 입장에서는 그냥 `boolean`을 반환하는 함수일 뿐이다.

"이 함수가 true를 반환하면 Admin이야"라는 사실을 TypeScript는 모른다.

type Person = { name: string };
type Admin = { name: string; role: "admin" };

// boolean만 쓰면 → filter 결과가 Person[] (타입이 좁혀지지 않음)
function isAdmin(person: Person): boolean {
  return (person as Admin).role === "admin";
}

const admins = people.filter(isAdmin); // Person[]

filter 결과가 여전히 `Person []`이다. TypeScript가 isAdmin이 뭘 걸러내는지 모르기 때문이다.

person is Admin을 쓰면 달라진다.

// person is Admin 쓰면 → filter 결과가 Admin[] (타입이 좁혀짐)
function isAdmin(person: Person): person is Admin {
  return (person as Admin).role === "admin";
}

const admins = people.filter(isAdmin); // Admin[] 

person is Admin은 일종의 계약서다.

"이 함수가 true를 반환하면, 그 값은 Admin이야"라고 TypeScript에게 명시적으로 알려주는 것이다.

이걸 Type Predicate라고 한다.

 

함수 오버로드(Function Overload)

"number를 넣으면 number를 반환하고, string을 넣으면 string을 반환한다." 이런 걸 표현하고 싶을 때 쓴다.

TypeScript는 구현부 본문을 보지 않는다. 선언된 타입만 본다.

그래서 아래처럼 구현 함수 하나만 있으면 TypeScript가 정확한 반환 타입을 알 수 없다.

// 이것만 있으면...
function double(x: number | string): number | string {
  if (typeof x === 'number') return x * 2;
  return x + x;
}

const a = double(5);    // number | string  (number인 걸 모름)
const b = double("hi"); // number | string  (string인 걸 모름)

그래서 계약서(오버로드 시그니처)를 위에 따로 써줘야 한다.

function double(x: number): number;           // 계약서 1
function double(x: string): string;           // 계약서 2
function double(x: number | string): number | string {  // 실제 구현
  if (typeof x === 'number') return x * 2;
  return x + x;
}

const a = double(5);    // number 
const b = double("hi"); // string 

TypeScript는 계약서(오버로드 시그니처)를 순서대로 보고 맞는 타입을 찾아준다.

구현부는 외부에서 직접 호출할 수 없다. 계약서를 통해서만 함수를 쓸 수 있는 것이다.

Async / Promise

`async`를 붙이면 반환값이 자동으로 `Promise`로 감싸진다. 굳이 `Promise.resolve()`로 감쌀 필요가 없다.

async function fetchUser(): Promise<User> {
  return { id: 1, name: "찰리" }; // 자동으로 Promise<User>가 됨
}

await는 반대로 Promise를 벗겨낸다.

const user = await fetchUser(); // Promise<User> → User

이 둘을 같이 쓰면 비동기 코드를 마치 동기 코드처럼 읽을 수 있어서 훨씬 직관적이다.

 

catch에서 e는 왜 unknown인가?

try {
  await fetchUser();
} catch (e) {
  // e는 unknown
  if (e instanceof Error) {
    console.error(e.message); 
  }
}

e가 unknown인 이유가 있다. JavaScript에서는 throw로 뭐든 던질 수 있다.

throw new Error("에러");  // Error 객체
throw "에러 발생";         // string
throw 42;                  // number
throw { code: 500 };       // 객체

어떤 타입이 날아올지 TypeScript가 알 수 없으니 unknown으로 받는 것이다. any였다면 그냥 넘어가겠지만, unknown이라서 타입을 확인하고 쓰도록 강제한다. 그래서 instanceof Error로 확인한 다음에 써야 한다.

 

 

"TypeScript는 선언된 타입만 본다"는 원칙에서 출발한다는 게 재밌다.

함수 이름을 믿지 않고, 구현부를 믿지 않고, 오직 선언된 타입만 믿는다.

그 원칙을 이해하고 나면 나머지가 자연스럽게 연결된다.

'프론트엔드 > Language' 카테고리의 다른 글

[JavaScript] 실행컨텍스트 & 클로저  (0) 2026.03.19
[JavaScript] 스코프(Scope)  (0) 2026.03.09
'프론트엔드/Language' 카테고리의 다른 글
  • [JavaScript] 실행컨텍스트 & 클로저
  • [JavaScript] 스코프(Scope)
yun_cic
yun_cic
  • yun_cic
    체대생의 개발 기록
    yun_cic
  • 전체
    오늘
    어제
    • 분류 전체보기 (33) N
      • 백엔드 (1)
      • 프로젝트 (6)
      • etc (5)
      • 대외활동 (1)
      • 강의자료 (5)
      • 프론트엔드 (6) N
        • Language (3)
        • Library (1) N
      • 우테코 (9)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • GitHub
    • 포트폴리오 페이지
  • 공지사항

  • 인기 글

  • 태그

    백엔드
    해커톤
    채널톡
    크롤링
    우아한테크코스 8기
    Crawling
    MySQL
    외주
    fastapi
    Python
    KUCC
    메모
    크몽
    개발자 #코딩 #체대생
    Selenium
    fe
    bs4
    우테코 8기
    todo
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
yun_cic
[TypeScript] 제너릭, Type Predicate, 함수 오버로드, async/await
상단으로

티스토리툴바