2장 기본형 답

프디·2025년 3월 30일

1. this 타입 지정하기

function toggleActive(this: HTMLButtonElement, e: MouseEvent) {
  this.classList.toggle('active');
}

const btn = document.querySelector('button');
btn?.addEventListener('click', toggleActive as EventListener);

설명:
브라우저는 this: HTMLButtonElement 같은 TypeScript 문법을 실행 중 이해하지 못하기 때문에,
런타임에서는 as HTMLButtonElement로 단언해야 함.
또한 as EventListener로 타입을 맞춰줘야 TypeScript와 DOM이 모두 만족.

2. 정확한 이벤트 리스너 타입 지정

type Listener = (this: HTMLButtonElement, e: MouseEvent) => void;

function addListener(listener: Listener) {
  const buttons = document.querySelectorAll('button');
    buttons.forEach((btn) => {
      btn.addEventListener('click', listener as EventListener);
  });

}

설명:
addEventListener의 콜백은 this를 넘기기 때문에, 명시적으로 this를 포함한 타입을 지정.
하지만 DOM은 EventListener 타입만 허용하므로 as EventListener 단언 필요.

3. ThisParameterType 활용

function toggle(this: HTMLDivElement, e: MouseEvent) {
this.classList.toggle("selected");
}

type ToggleThis = ThisParameterType<typeof toggle>; // HTMLDivElement

설명:
typeof toggle로 함수 타입을 가져오고, ThisParameterType으로 this 타입만 추출.

4. keyof 오류 해결

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

function printUser(user: User) {
for (const key in user) {
  console.log(`${key}: ${user[key as keyof User]}`);
}
}

설명:
for...in의 key는 string 타입이라서 keyof User와 정확히 일치하지 않음.
key as keyof User 단언을 통해 안전하게 접근 가능.

5. object vs Object 차이

정답: C. 기본형은 허용하지 않음

설명:

object: {} 타입만 허용, "hello", 42는 허용하지 않음

Object: 모든 타입 허용 (string, number, boolean, null, undefined, 함수 등)

6. 구조적 타입 호환

type Person = { name: string; age: number };

const personWithExtra = {
  name: "Alice",
  age: 30,
  address: "Seoul",
};

printPerson(personWithExtra); // OK

function printPerson(p: Person) {
  console.log(p.name, p.age);
}

설명:
TypeScript는 구조적 타입 시스템이므로, 타입의 이름이 아니라 모양이 일치하면 할당 가능.

7. 인터페이스 병합 이슈

// ❌ 이 인터페이스는 DOM 내장 FormData와 병합됨
// interface FormData { name: string; age: number; }

// ✅ 수정
type FormData = {
  name: string;
  age: number;
};

function submitForm(data: FormData) {
  console.log(data.name); // OK
}

설명:
전역에 이미 존재하는 DOM의 FormData와 인터페이스 이름이 충돌함.
type alias를 쓰면 병합되지 않아 안전함.
(브라우저에는 이미 전역으로 존재하는 DOM 인터페이스 FormData가 있다. 같은 이름의 interface를 만들면 자동으로 병합(declaration merging) 된다. 이때 FormData.entries() 같은 원래 DOM API의 메서드(내가 만든 FormData 에 존재하지 메서드를 쓸 수 있는 것처럼 보이게 됨. 이로 인해 컴파일 에러 없이 런타임 에러가 발생할 수 있음.)

8. 튜플로 타입 고정

type User = [string, number];
const user: User = ["Bob", 25];
  • 설명:
    배열이 아닌 튜플을 사용하면, 요소 개수와 각 요소의 타입을 정확히 고정할 수 있어 구조적 안정성이 높아짐.

9. unique symbol로 명목적 타입 구분

declare const userIdBrand: unique symbol;
declare const productIdBrand: unique symbol;

type UserId = number & { [userIdBrand]: void };
type ProductId = number & { [productIdBrand]: void };

function getUser(id: UserId) {}
function getProduct(id: ProductId) {}

const uid = 123 as UserId;
const pid = 123 as ProductId;

getUser(uid);       // ✅
getProduct(pid);    // ✅
// getUser(pid);    // ❌ 타입 오류 발생

설명:
TypeScript는 기본적으로 구조적 타입 시스템이라 number는 구분되지 않음.
unique symbol을 사용해 명목적 타입 시스템처럼 작동시켜 타입 간 구분 가능.

10. 함수 오버로드

  
type LogLevel = 'info' | 'warn' | 'error';

function logMessage(message: string): void;
function logMessage(level: LogLevel, message: string): void;
function logMessage(param1: string, param2?: string): void {
  const levels: LogLevel[] = ['info', 'warn', 'error'];

  if (param2 === undefined) {
    console.log(`[info] ${param1}`);
  } else if (levels.includes(param1 as LogLevel)) {
    console.log(`[${param1}] ${param2}`);
  } else {
    throw new Error(`Invalid log level: ${param1}`);
  }
}

설명:
두 가지 호출 케이스를 지원하려면 오버로드 시그니처가 필요.
logMessage("warn", "be careful") 같은 케이스에 대해 param1이 LogLevel인지 체크 필요.

profile
프론트엔드개발자인디

0개의 댓글