본문 바로가기
2024~/React

[타입스크립트] 타입 활용하기 - extends, as, infer

by Or0i쿠 2025. 6. 15.

1. extends 키워드

여러 가지 뜻으로 쓰임. 문맥 따라 다름.

  • 인터페이스/클래스 확장 (상속):
    • 이게 제일 흔함. 어떤 인터페이스가 다른 인터페이스 기능 물려받거나, 어떤 클래스가 다른 클래스 기능 물려받을 때 씀.
    • 예시 (인터페이스): interface Dog extends Animal { ... } -> Dog이 Animal 기능 물려받음.
    • 예시 (클래스): class Dog extends Animal { ... } -> Dog 클래스가 Animal 클래스 상속받음.
//예시 (인터페이스 확장):
interface Animal {
  live(): void;
}

interface Dog extends Animal { // ✨ Dog 인터페이스는 Animal 인터페이스를 확장합니다.
  woof(): void;
}

const myDog: Dog = {
  live: () => console.log("살아있다"),
  woof: () => console.log("멍멍"),
};

//예시 (클래스 상속):
class Animal {
  live() { console.log("살아있다"); }
}

class Dog extends Animal { // ✨ Dog 클래스는 Animal 클래스를 상속합니다.
  woof() { console.log("멍멍"); }
}

const myDog = new Dog();
myDog.live(); // 상속받은 메서드 사용 가능
myDog.woof();
  • 제네릭 타입 제약:
    • <T extends 어떤타입> 이런 식으로 씀. 제네릭 타입 T가 어떤타입의 자식 타입이거나 어떤타입 조건을 만족해야 한다고 제한 걸 때 씀.
    • 예시: function logLength<T extends string | number>(value: T) -> T는 string이나 number만 가능함. 다른 타입 오면 에러 남.
// T는 string 또는 number 타입이어야 함을 제약합니다.
function logLength<T extends string | number>(value: T): void {
  // T가 string 또는 number이므로 length 속성에 안전하게 접근할 수 없습니다.
  // string 타입일 때만 length에 접근 가능하도록 조건부 타입이나 오버로딩 필요
  // console.log(value.length); // ✨ 에러 발생 (number는 length가 없음)
}

logLength("hello"); // 허용
logLength(123);    // 허용
// logLength(true);   // ✨ 에러 발생 (boolean은 string | number를 확장하지 않음)
  • 조건부 타입 조건:
    • T extends U ? X : Y 이런 문법에서 조건 부분임. T 타입이 U 타입에 할당 가능한지 아닌지 따질 때 씀.
    • 예시: type IsString<T> = T extends string ? string : number; -> T가 string이면 string 타입, 아니면 number 타입 됨.
// T가 string 타입이면 string, 아니면 number 타입
type IsString<T> = T extends string ? string : number;

type A = IsString<string>; // A는 string 타입
type B = IsString<number>; // B는 number 타입
type C = IsString<boolean>; // C는 number 타입

2. as 키워드

  • 타입 단언:
    • 개발자가 컴파일러보다 타입 더 잘 안다고 확신할 때 씀. "야, 이 값 타입은 내가 말한 이거 맞아!" 하고 컴파일러한테 강제로 알려주는 거임.
    • 주의: 이건 컴파일러한테만 알려주는 거지, 실제 자바스크립트 코드에는 영향 없음. 잘못된 타입으로 단언하면 나중에 프로그램 돌릴 때 에러 날 수 있으니 조심해야 함.
    • 예시: const someValue: any = "abc"; const strLength = (someValue as string).length; -> someValue가 any지만 string이라고 단언하고 length 속성 씀.
const someValue: any = "this is a string";

// any 타입이지만 string 타입으로 간주하겠다고 단언
const strLength: number = (someValue as string).length; // ✨ as string 사용

const anotherValue: unknown = 123;

// unknown 타입이지만 number 타입으로 간주하겠다고 단언
const numValue: number = anotherValue as number; // ✨ as number 사용

// DOM 요소 가져오기 (HTMLElement일 수 있지만 HTMLInputElement로 단언)
const inputElement = document.getElementById('myInput') as HTMLInputElement; // ✨ as HTMLInputElement 사용
console.log(inputElement.value); // inputElement가 HTMLInputElement임을 확신할 때 사용

3. infer 키워드

  • 조건부 타입 안에서만 씀:
    • T extends ... infer R ... ? R : ... 이런 조건부 타입 문법 안에서만 나옴.
    • 목적: 기존 타입 구조 분석해서 그 안에 있는 특정 타입을 **추론(infer)**해서 R 같은 새 타입 변수에 담을 때 씀. 이 R은 그 조건부 타입 구문 안에서만 쓸 수 있음.
    • 예시 (함수 반환 타입 뽑기): type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never; -> T가 함수 타입이면 infer R로 그 함수의 반환 타입을 R에 담고, 그 R 타입을 최종 결과로 줌.
    • 예시 (배열 요소 타입 뽑기): type GetArrayElement<T> = T extends (infer ElementType)[] ? ElementType : T; -> T가 배열 타입이면 infer ElementType으로 배열 요소 타입을 ElementType에 담고, 그 ElementType 타입을 최종 결과로 줌.
// 예시 (함수의 반환 타입 추출):

// ReturnType<T> 유틸리티 타입의 내부 동작과 유사
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never; // ✨ infer R 사용

function getUser(): { id: number; name: string } {
  return { id: 1, name: 'Alice' };
}

type UserReturnType = GetReturnType<typeof getUser>; // ✨ UserReturnType는 { id: number; name: string } 타입

type StringReturnType = GetReturnType<() => string>; // ✨ StringReturnType는 string 타입

type NumberReturnType = GetReturnType<number>; // ✨ NumberReturnType는 never 타입 (함수 형태가 아니므로)

// 예시 (배열 요소 타입 추출):

type GetArrayElement<T> = T extends (infer ElementType)[] ? ElementType : T; // ✨ infer ElementType 사용

type StringArrayElement = GetArrayElement<string[]>; // ✨ StringArrayElement는 string 타입
type NumberArrayElement = GetArrayElement<number[]>; // ✨ NumberArrayElement는 number 타입
type BooleanArrayElement = GetArrayElement<boolean[]>; // ✨ BooleanArrayElement는 boolean 타입
type StringElement = GetArrayElement<string>; // ✨ StringElement는 string 타입 (배열이 아니므로 T 그대로 반환)