출시·고도화 중
TypeScript 안내서 · 6/6
TypeScript의 진짜 힘은 타입 시스템에 있습니다. 이 장에서는 재사용 가능한 타입을 만드는 제네릭, 코드 흐름에 따라 타입을 좁히는 내로잉과 타입 가드, 상태를 안전하게 표현하는 구별된 유니언, 기존 타입을 변형하는 유틸리티 타입, 그리고 이 모든 것을 제대로 작동하게 하는 strict 모드를 살펴봅니다.
제네릭은 타입을 매개변수로 받는 방법입니다. 어떤 타입이 들어와도 그 타입 정보를 잃지 않고 그대로 돌려줄 수 있습니다. extends로 타입 매개변수에 조건을 걸 수 있고, keyof와 조합하면 객체의 키와 값 타입을 정확히 연결할 수 있습니다.
function first<T>(items: T[]): T | undefined {
return items[0];
}
const n = first([1, 2, 3]); // number | undefined
const s = first(["a", "b"]); // string | undefined
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest("hello", "hi"); // string
longest([1, 2], [1, 2, 3]); // number[]
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Kim", age: 30 };
const age = getProp(user, "age"); // number
// getProp(user, "email"); // 오류: user에 없는 키
interface ApiResponse<T> {
data: T;
status: number;
}any를 쓰면 무엇이든 받을 수 있지만 반환 타입 정보가 사라집니다. 제네릭은 유연함과 타입 안전성을 함께 얻는 방법입니다.
유니언 타입의 값은 확인 코드를 거치면서 더 구체적인 타입으로 좁혀집니다. 컴파일러는 typeof, instanceof, in, 동등 비교, 참거짓 검사를 이해합니다. 직접 만든 검사 함수는 반환 타입에 value is 타입 형태의 타입 서술어를 적어 타입 가드로 만듭니다.
interface Cat { meow(): void }
interface Dog { bark(): void }
function speak(pet: Cat | Dog) {
if ("meow" in pet) pet.meow(); // Cat
else pet.bark(); // Dog
}
function isString(value: unknown): value is string {
return typeof value === "string";
}
function shout(input: unknown) {
if (isString(input)) {
console.log(input.toUpperCase()); // string으로 좁혀짐
}
}
function len(text: string | null): number {
if (!text) return 0; // null과 빈 문자열을 걸러냄
return text.length; // string
}공통 속성(보통 kind나 type)에 서로 다른 리터럴 값을 둔 유니언을 구별된 유니언이라고 합니다. switch로 그 속성을 확인하면 각 분기에서 정확한 타입이 결정됩니다. default에서 never에 대입하면 새 경우를 추가했을 때 빠뜨린 곳을 컴파일러가 찾아 줍니다.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; size: number }
| { kind: "rect"; width: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2;
case "square": return shape.size ** 2;
case "rect": return shape.width * shape.height;
default: {
const unreachable: never = shape; // 빠진 경우가 있으면 오류
return unreachable;
}
}
}로딩 · 성공 · 실패 같은 화면 상태나 서버 메시지 종류를 표현할 때 특히 유용합니다.
TypeScript는 기존 타입을 바꿔 새 타입을 만드는 유틸리티 타입을 기본으로 제공합니다.
| 유틸리티 | 하는 일 |
|---|---|
Partial<T> | 모든 속성을 선택 속성으로 |
Required<T> | 모든 속성을 필수로 |
Readonly<T> | 모든 속성을 읽기 전용으로 |
Pick<T, K> · Omit<T, K> | 일부 속성만 고르거나 빼기 |
Record<K, V> | 키 K, 값 V인 객체 |
ReturnType<F> · Parameters<F> | 함수의 반환 · 매개변수 타입 |
Awaited<T> | Promise가 풀린 값의 타입 |
interface Todo {
id: number;
title: string;
done: boolean;
}
type TodoPatch = Partial<Omit<Todo, "id">>; // title?, done?
type TodoPreview = Pick<Todo, "id" | "title">;
type ByStatus = Record<"open" | "closed", Todo[]>;
function update(todo: Todo, patch: TodoPatch): Todo {
return { ...todo, ...patch };
}
const config = {
port: 3000,
mode: "dev",
} satisfies Record<string, string | number>;마지막의 satisfies는 값이 타입 조건을 만족하는지 검사하면서도, 변수 타입은 넓히지 않고 추론된 구체적인 타입을 유지합니다.
"strict": true는 여러 검사 옵션을 한꺼번에 켭니다. 그중 가장 중요한 것은 다음과 같습니다.
strictNullChecks: null과 undefined를 별도 타입으로 다뤄, 없는 값을 확인 없이 쓰는 실수를 막습니다.noImplicitAny: 타입을 추론할 수 없을 때 조용히 any가 되는 것을 막습니다.strictPropertyInitialization: 클래스 속성이 생성자에서 초기화되었는지 검사합니다.useUnknownInCatchVariables: catch 변수를 unknown으로 둡니다.strict에 포함되지 않지만 함께 켜 두면 좋은 옵션도 있습니다. noUncheckedIndexedAccess는 배열이나 인덱스 시그니처로 읽은 값에 undefined 가능성을 더해 줍니다.
typeof, instanceof, in 등으로 유니언을 좁히며, value is T로 타입 가드를 직접 만들 수 있습니다.never 검사로 모든 경우를 빠짐없이 처리합니다.Partial, Pick, Omit, Record 같은 유틸리티 타입으로 타입을 변형합니다.strict는 항상 켜 둡니다.더 깊이 공부하려면 다음 공식 문서를 참고하세요.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.