타입 검사를 더하는 이유
TypeScript가 새 런타임이 아니라 JavaScript 위의 설계/검사 층이라는 점, 타입이 특히 어디에서 가치를 만드는지, 런타임 검증과의 경계를 정리합니다.
function add(a: number, b: number) {
return a + b;
}
const total = add(1, 2);
// const broken = add("1", 2);Category
Preparing references and filters for this topic. 이 주제의 레퍼런스와 필터를 준비하고 있습니다.
Category Reference
타입 시스템, 제네릭, mapped/conditional types, discriminated union, 모듈 해석, tsconfig까지 TypeScript의 핵심 흐름을 카드형 레퍼런스로 정리합니다.
Search titles, summaries, tags, and subcategories.
Showing 32 cards.
Subcategory
2 cards
TypeScript가 새 런타임이 아니라 JavaScript 위의 설계/검사 층이라는 점, 타입이 특히 어디에서 가치를 만드는지, 런타임 검증과의 경계를 정리합니다.
function add(a: number, b: number) {
return a + b;
}
const total = add(1, 2);
// const broken = add("1", 2);타입을 어디까지 직접 적고 어디서 추론에 맡길지, 함수 경계와 지역 변수의 차이, 빈 값과 공개 API에서 명시가 필요한 이유를 정리합니다.
let username: string = "refdock";
const port = 3000;
function repeat(text: string, times: number): string {
return text.repeat(times);
}9 cards
string, number, boolean, array, tuple를 각각 언제 쓰는지, 배열과 튜플의 경계, 원시 타입 표기를 어떻게 읽어야 하는지 정리합니다.
let title: string = "RefDock";
let count: number = 42;
let done: boolean = false;
let tags: string[] = ["ts", "types"];
let position: [number, number] = [10, 20];여러 가능성을 하나의 타입으로 묶는 union, 가능한 값 집합을 고정하는 literal 타입, 그리고 왜 바로 narrowing이 따라오는지 정리합니다.
type Status = "idle" | "loading" | "success" | "error";
function setStatus(status: Status) {
return status;
}union 타입 값을 실제로 안전하게 쓰기 위해 어떻게 narrowing하는지, `typeof`, `instanceof`, `in`, 사용자 정의 타입 가드를 언제 쓰는지 정리합니다.
function printId(id: string | number) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id.toFixed(0));
}
}`any`가 타입 검사를 어떻게 무너뜨리는지, 외부 입력에는 왜 `unknown`이 더 맞는지, `never`가 exhaustiveness 체크에 어떻게 쓰이는지 정리합니다.
const raw: unknown = JSON.parse("{}");
if (typeof raw === "object" && raw !== null) {
console.log(raw);
}
function fail(message: string): never {
throw new Error(message);
}튜플을 배열과 어떻게 구분해서 써야 하는지, `readonly` 배열이 입력 계약을 어떻게 더 정직하게 만드는지, 깊은 불변성과 어디서 갈리는지 정리합니다.
const point: [number, number] = [10, 20];
const tags: readonly string[] = ["ts", "types"];공통 태그 필드로 상태를 모델링하는 discriminated union과, 새 케이스 누락을 `never`로 잡는 exhaustiveness 체크를 어떻게 같이 쓰는지 정리합니다.
type Result =
| { kind: "ok"; value: string }
| { kind: "error"; message: string };
function render(result: Result) {
switch (result.kind) {
case "ok":
return result.value;
case "error":
return result.message;
}
}런타임 검증이 타입 시스템에 정보를 전달하는 두 방식인 `value is T`와 `asserts value is T`를 어떤 흐름에서 각각 써야 하는지 정리합니다.
function isString(value: unknown): value is string {
return typeof value === "string";
}
function assertString(value: unknown): asserts value is string {
if (typeof value !== "string") {
throw new Error("expected string");
}
}enum을 언제 쓸지, literal union이나 `as const` 객체와 어디서 갈리는지, string enum과 `const enum`의 실전 선택 기준을 정리합니다.
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
function move(dir: Direction) {
console.log(dir);
}intersection을 union의 반대축으로 읽는 법, 객체 타입 조합에 언제 쓰는지, 충돌 키가 `never`로 무너지는 지점을 정리합니다.
type Named = { name: string };
type Aged = { age: number };
type Person = Named & Aged;5 cards
`type`과 `interface`를 무엇이 더 낫냐로 보지 않고, 객체 계약 중심인지 타입 조합 중심인지에 따라 어떻게 나눠 쓰는지 정리합니다.
interface User {
id: string;
name: string;
}
type UserWithRole = User & {
role: "admin" | "member";
};optional과 readonly가 객체 계약에서 각각 무엇을 제한하는지, 입력의 유연성과 출력의 안정성을 어떻게 나눠 설계하는지 정리합니다.
interface User {
id: string;
nickname?: string;
readonly createdAt: Date;
}함수 시그니처를 타입으로 어떻게 읽는지, 콜백 계약을 언제 별도 타입으로 뽑는지, `void`와 선택 매개변수가 호출 계약을 어떻게 바꾸는지 정리합니다.
type Ready = () => void;
type Formatter = (value: number) => string;
type Join = (...parts: string[]) => string;
function formatPrice(value: number, fn: Formatter) {
return fn(value);
}여러 호출 형태를 API에 어떻게 드러낼지, overload가 필요한 경우와 union으로 충분한 경우, call signature가 함수 객체를 모델링할 때 왜 필요한지 정리합니다.
function format(value: number): string;
function format(value: Date): string;
function format(value: number | Date): string {
return value instanceof Date ? value.toISOString() : value.toFixed(2);
}`async` 함수 반환 타입을 어떻게 읽어야 하는지, `Promise<T>`와 `Awaited<T>`의 역할 차이, 비동기 함수 계약을 설계할 때 무엇을 먼저 봐야 하는지 정리합니다.
async function fetchName(): Promise<string> {
return "Mina";
}
type Name = Awaited<ReturnType<typeof fetchName>>;7 cards
제네릭을 `any`의 대체가 아니라 입력-출력 관계를 보존하는 도구로 읽는 법, 함수와 타입에서 언제 제네릭이 필요한지 정리합니다.
function identity<T>(value: T): T {
return value;
}
const n = identity(1);
const s = identity("hello");제네릭에 필요한 최소 조건을 거는 방법, `keyof`로 유효한 키 집합을 만드는 방식, `T[K]`로 키와 값 타입 관계를 보존하는 패턴을 정리합니다.
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: "u1", name: "Mina" };
const name = getValue(user, "name");`Partial`, `Required`, `Readonly`, `Pick`, `Omit`, `Record`를 새 문법 암기가 아니라 기존 타입을 목적에 맞게 재가공하는 도구로 읽는 법을 정리합니다.
interface User {
id: string;
name: string;
email: string;
}
type UserPatch = Partial<User>;
type UserSummary = Pick<User, "id" | "name">;이미 있는 값과 타입에서 정보를 다시 꺼내 재사용하는 `typeof`, `T[K]`, `typeof arr[number]` 패턴을 어떻게 읽는지 정리합니다.
const routes = {
home: "/",
about: "/about",
} as const;
type Routes = typeof routes;
type RouteName = keyof Routes;
type RoutePath = Routes[RouteName];기존 객체 타입 전체를 기계적으로 변형하는 mapped type과, 키 이름 자체를 바꾸는 key remapping을 언제 써야 하는지 정리합니다.
type ReadonlyUser<T> = {
readonly [K in keyof T]: T[K];
};
type PrefixKeys<T> = {
[K in keyof T as `app_${string & K}`]: T[K];
};타입 수준 분기인 conditional type을 언제 써야 하는지, `infer`로 타입 내부 조각을 어떻게 추출하는지, 과도한 중첩을 피하는 기준을 정리합니다.
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;
type ReturnOf<T> = T extends (...args: any[]) => infer R ? R : never;문자열 패턴을 타입으로 모델링하는 template literal types를 언제 써야 하는지, plain literal union과 어디서 갈리는지 정리합니다.
type EventName = "click" | "focus";
type HandlerName = `on${Capitalize<EventName>}`;6 cards
`tsconfig.json`을 옵션 목록이 아니라 프로젝트의 타입 검사/출력 정책 파일로 읽는 법, `strict`, `target`, `module`, `noEmit`, 파일 범위 옵션의 역할을 정리합니다.
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"strict": true,
"noEmit": true
}
}`strict`가 왜 품질 기준선이 되는지, `strictNullChecks`가 런타임 버그를 어떻게 줄이는지, 오류를 `as any`가 아니라 모델링과 narrowing으로 줄여야 하는 이유를 정리합니다.
{
"compilerOptions": {
"strict": true
}
}`.d.ts`가 런타임 코드 없이 타입 계약만 제공하는 이유, 패키지 내장 타입과 `@types` 생태계를 어떻게 구분해 읽는지, 얇은 선언 파일을 언제 직접 써야 하는지 정리합니다.
// index.d.ts
declare module "legacy-lib" {
export function parse(input: string): unknown;
}`satisfies`와 `as const`를 둘 다 '고정' 도구로 보지 않고, 계약 검사와 리터럴 보존이라는 서로 다른 역할로 구분하는 기준을 정리합니다.
type Route = {
path: string;
secure: boolean;
};
const routes = {
home: { path: "/", secure: false },
admin: { path: "/admin", secure: true },
} satisfies Record<string, Route>;import 경로가 실제 파일로 해석되는 규칙, `paths` alias를 언제 쓰는지, editor만 되고 런타임이 깨지는 문제를 어떻게 읽는지 정리합니다.
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}기존 JavaScript 코드베이스에 TypeScript 검사를 점진 도입하는 흐름, `allowJs`와 `checkJs`, JSDoc 기반 타입 계약을 어디부터 붙일지 정리합니다.
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"noEmit": true
}
}3 cards
TypeScript 클래스가 런타임 값과 인스턴스 타입 계약을 함께 만든다는 점, 클래스가 맞는 문제와 아닌 문제, 접근 제한자와 생성자 패턴을 정리합니다.
class User {
constructor(
public id: string,
public name: string,
) {}
greet() {
return `Hello, ${this.name}`;
}
}파일 단위 모듈 경계가 왜 중요한지, named/default export를 어떻게 고를지, `import type`으로 타입 전용 의존성을 어떻게 분리하는지 정리합니다.
// user.ts
export interface User {
id: string;
name: string;
}
export type UserId = User["id"];
export function formatUser(user: User) {
return `${user.id}: ${user.name}`;
}
// app.ts
import { formatUser, type User } from "./user";같은 이름의 선언을 TypeScript가 어떻게 병합하는지, interface 병합과 module augmentation, `declare global`이 실제로 언제 필요한지 정리합니다.
interface User {
id: number;
name: string;
}
interface User {
email: string;
role: "admin" | "user";
}