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이 모두 만족.
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 단언 필요.
function toggle(this: HTMLDivElement, e: MouseEvent) {
this.classList.toggle("selected");
}
type ToggleThis = ThisParameterType<typeof toggle>; // HTMLDivElement
설명:
typeof toggle로 함수 타입을 가져오고, ThisParameterType으로 this 타입만 추출.
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 단언을 통해 안전하게 접근 가능.
정답: C. 기본형은 허용하지 않음
설명:
object: {} 타입만 허용, "hello", 42는 허용하지 않음
Object: 모든 타입 허용 (string, number, boolean, null, undefined, 함수 등)
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는 구조적 타입 시스템이므로, 타입의 이름이 아니라 모양이 일치하면 할당 가능.
// ❌ 이 인터페이스는 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 에 존재하지 메서드를 쓸 수 있는 것처럼 보이게 됨. 이로 인해 컴파일 에러 없이 런타임 에러가 발생할 수 있음.)
type User = [string, number];
const user: User = ["Bob", 25];
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을 사용해 명목적 타입 시스템처럼 작동시켜 타입 간 구분 가능.
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인지 체크 필요.