1. TypeScript란?
TypeScript는 Microsoft가 개발한 JavaScript의 상위 집합(Superset). 모든 JavaScript 코드는 유효한 TypeScript 코드이며, TypeScript는 컴파일 시 JavaScript로 변환됨.
JavaScript vs TypeScript
// JavaScript — 런타임에 터져야 알스 다음
function add(a, b) {
return a + b;
}
add(1, '2'); // '12' — 의도치 않은 문자열 결합
// TypeScript — 컴파일 시점에 오류 감지
function add(a: number, b: number): number {
return a + b;
}
add(1, '2'); // Error: Argument of type 'string' is not assignable to parameter of type 'number'
JavaScript
복사
TypeScript를 써야 하는 이유
•
컴파일 시점 오류 발견 — 런타임 버그를 사전에 막음
•
IDE 자동완성 / 코드 힌트 — 개발 생산성 대폭 향상
•
코드 가독성 — 타입이 문서 역할
•
대규모 프로젝트 유지보수 — 리팩토링이 안전함
2. 기본 타입
Primitive Types
// 기본 원시 타입
let name: string = 'codesche';
let age: number = 30;
let isActive: boolean = true;
let nothing: null = null;
let undef: undefined = undefined;
// 타입 추론 — 다음과 같이 써도 TS가 자동 추론
let name = 'codesche'; // string
let age = 30; // number
let isActive = true; // boolean
TypeScript
복사
any, unknown, never, void
// any: 타입 검사 포기 — 가능하면 사용 지양
let anything: any = 42;
anything = 'hello'; // OK
anything.foo.bar.baz; // OK (컴파일 통과, 런타임 실패 가능)
// unknown: any의 안전한 대안 — 사용 전 타입 좌소(narrowing) 필수
let value: unknown = fetchSomething();
if (typeof value === 'string') {
console.log(value.toUpperCase()); // narrowing 후 안전
}
// void: 반환값이 없는 함수
function logMessage(msg: string): void {
console.log(msg);
}
// never: 절대 도달할 수 없는 타입 (다 커버된 switch 등)
function throwError(msg: string): never {
throw new Error(msg);
}
TypeScript
복사
3. 배열과 튜플
// 배열 타입
const nums: number[] = [1, 2, 3];
const strs: Array<string> = ['a', 'b', 'c'];
// 다차원 배열
const matrix: number[][] = [[1, 2], [3, 4]];
// 튜플 (Tuple): 길이와 타입이 고정된 배열
const point: [number, number] = [10, 20];
const entry: [string, number] = ['age', 30];
// 네이밍된 튜플 (TS 4.0+)
const rgb: [r: number, g: number, b: number] = [255, 128, 0];
// 튜플 실무 예: useState 반환 타입 표현
function useState<T>(initial: T): [T, (val: T) => void] {
let state = initial;
const setState = (val: T) => { state = val; };
return [state, setState];
}
const [count, setCount] = useState(0); // [number, (val: number) => void]
TypeScript
복사
4. 인터페이스(Interface)와 타입 별칭(Type Alias)
Interface
interface User {
id: number;
name: string;
email: string;
role?: 'admin' | 'user'; // 선택적 프로퍼티
readonly createdAt: Date; // 읽기 전용
}
// 확장 (extends)
interface Admin extends User {
permissions: string[];
}
// 선언 합치 — 같은 이름으로 여러 번 선언 가능
interface User {
phone?: string;
}
TypeScript
복사
Type Alias
type Point = { x: number; y: number; };
// 유니온, 리터럴 타입에 유리
type ID = string | number;
type Status = 'pending' | 'active' | 'inactive';
type Nullable<T> = T | null;
// 함수 타입
type Callback = (err: Error | null, result: string) => void;
TypeScript
복사
Interface vs Type Alias 비교
구분 | Interface | Type Alias |
확장 | extends | & (인터섹션) |
합치 | 가능 (Declaration Merging) | 불가 |
유니온 타입 | 불가 | 가능 |
권장 상황 | 객체/클래스 형태 | 복합타입, 유틸리티 타입 |
5. 유니온(Union)과 인터섹션(Intersection)
// Union: A 또는 B
type StringOrNumber = string | number;
type Result = 'success' | 'error' | 'loading';
function formatId(id: string | number): string {
return id.toString();
}
// Intersection: A 그리고 B 모두
type Timestamped = { createdAt: Date; updatedAt: Date; };
type Named = { name: string; };
type NamedTimestamped = Named & Timestamped;
// Discriminated Union — 실무에서 매우 유용
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number }
| { kind: 'triangle'; base: number; height: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.radius ** 2;
case 'rectangle': return shape.width * shape.height;
case 'triangle': return (shape.base * shape.height) / 2;
}
}
TypeScript
복사
6. 열거형(Enum)
// 숫자형 Enum
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
// 문자열 Enum — 실무에서 더 자주 씀
enum Status {
Pending = 'PENDING',
Active = 'ACTIVE',
Inactive = 'INACTIVE',
}
function handleStatus(status: Status) {
if (status === Status.Active) {
console.log('활성 상태');
}
}
// const enum: 컴파일 시 인라인 대체 — 번들 파일 크기 최소화
const enum HttpMethod {
GET = 'GET',
POST = 'POST',
PUT = 'PUT',
DELETE = 'DELETE',
}
TypeScript
복사
7. 제네릭(Generics)
타입을 매개변수처럼 다루는 기법. 다양한 타입에 재사용 가능한 코드를 작성할 수 있음.
기본 제네릭
function identity<T>(value: T): T {
return value;
}
identity<string>('hello'); // 명시
identity(42); // 추론: number로 자동 추론
// 제네릭 인터페이스
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
type UserResponse = ApiResponse<User>;
type OrderResponse = ApiResponse<Order[]>;
TypeScript
복사
제네릭 제약 (Constraints)
// T는 반드시 length 프로퍼티를 가져야 함
function getLength<T extends { length: number }>(value: T): number {
return value.length;
}
getLength('hello'); // 5
getLength([1, 2, 3]); // 3
getLength(42); // Error
// keyof 제약: 객체의 키만 받도록
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: 'codesche', email: 'a@b.com' };
getProperty(user, 'name'); // string
getProperty(user, 'phone'); // Error
TypeScript
복사
제네릭 유틸리티 함수
// 배열 중 특정 키로 그룹화
function groupBy<T>(arr: T[], key: keyof T): Record<string, T[]> {
return arr.reduce((acc, item) => {
const groupKey = String(item[key]);
acc[groupKey] = acc[groupKey] ?? [];
acc[groupKey].push(item);
return acc;
}, {} as Record<string, T[]>);
}
const users = [
{ name: 'Alice', role: 'admin' },
{ name: 'Bob', role: 'user' },
{ name: 'Carol', role: 'admin' },
];
groupBy(users, 'role');
// { admin: [{...}, {...}], user: [{...}] }
TypeScript
복사
8. 함수 타입
// 기본
function add(a: number, b: number): number { return a + b; }
const add = (a: number, b: number): number => a + b;
// 선택적 매개변수
function greet(name: string, greeting?: string): string {
return `${greeting ?? 'Hello'}, ${name}!`;
}
// 기본값 매개변수
function createUser(name: string, role: string = 'user') { /* ... */ }
// 나머지 매개변수
function sum(...nums: number[]): number {
return nums.reduce((a, b) => a + b, 0);
}
// 함수 오버로드 — 입력에 따라 다른 반환 타입
function parse(value: string): number;
function parse(value: number): string;
function parse(value: string | number): string | number {
return typeof value === 'string' ? parseInt(value) : String(value);
}
const num = parse('42'); // number
const str = parse(42); // string
TypeScript
복사
9. 유틸리티 타입 (Utility Types)
interface User {
id: number;
name: string;
email: string;
password: string;
role: 'admin' | 'user';
}
// Partial<T>: 모든 프로퍼티를 선택적으로
type UserUpdate = Partial<User>;
// Required<T>: 모든 프로퍼티를 필수로
type StrictUser = Required<User>;
// Readonly<T>: 읽기 전용
type ImmutableUser = Readonly<User>;
// Pick<T, K>: 특정 키만 선택
type UserPreview = Pick<User, 'id' | 'name'>;
// Omit<T, K>: 특정 키 제외
type PublicUser = Omit<User, 'password'>;
// Record<K, V>: 키-값 객체 타입
type RolePermissions = Record<'admin' | 'user', string[]>;
// Exclude<T, U>: T에서 U를 제외
type NonAdmin = Exclude<'admin' | 'user' | 'guest', 'admin'>;
// 'user' | 'guest'
// Extract<T, U>: T에서 U만 추출
type OnlyAdmin = Extract<'admin' | 'user' | 'guest', 'admin' | 'user'>;
// 'admin' | 'user'
// NonNullable<T>: null, undefined 제외
type SafeString = NonNullable<string | null | undefined>; // string
// ReturnType<T>: 함수 반환 타입 추출
type AddResult = ReturnType<typeof add>; // number
// Parameters<T>: 함수 매개변수 타입 추출
type AddParams = Parameters<typeof add>; // [number, number]
TypeScript
복사
10. 타입 가드와 Narrowing
// typeof 가드
function process(value: string | number) {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value.toFixed(2);
}
// instanceof 가드
function handleError(err: Error | TypeError) {
if (err instanceof TypeError) {
console.log('타입 에러:', err.message);
} else {
console.log('일반 에러:', err.message);
}
}
// in 연산자 가드
interface Cat { meow(): void; }
interface Dog { bark(): void; }
function speak(animal: Cat | Dog) {
if ('meow' in animal) {
animal.meow();
} else {
animal.bark();
}
}
// 커스텀 타입 가드 (Type Predicate)
function isString(value: unknown): value is string {
return typeof value === 'string';
}
// 단언 함수 (Assertion Function)
function assertDefined<T>(value: T | null | undefined): asserts value is T {
if (value == null) throw new Error('Value가 null입니다');
}
TypeScript
복사
11. 클래스(Class)와 타입
abstract class Animal {
constructor(
public name: string, // public: 외부 접근 가능
protected age: number, // protected: 자신 + 자식
private readonly id: string, // private: 자신만, 읽기 전용
) {}
abstract speak(): string;
describe(): string {
return `${this.name}, ${this.age}살`;
}
}
class Dog extends Animal {
constructor(name: string, age: number, private breed: string) {
super(name, age, crypto.randomUUID());
}
speak(): string { return `${this.name}: 멍!`; }
}
// 인터페이스 구현
interface Serializable {
serialize(): string;
}
class User implements Serializable {
constructor(
public readonly id: number,
public name: string,
private password: string,
) {}
serialize(): string {
return JSON.stringify({ id: this.id, name: this.name });
}
}
TypeScript
복사
12. 고급 타입 활용
Mapped Types
// 커스텀 Partial 구현
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// 모든 값을 string으로 변환
type Stringify<T> = {
[K in keyof T]: string;
};
// 수정 불가 버전
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
TypeScript
복사
Conditional Types
type IsString<T> = T extends string ? 'yes' : 'no';
type A = IsString<string>; // 'yes'
type B = IsString<number>; // 'no'
// Flatten: 배열이면 원소 타입 추출
type Flatten<T> = T extends (infer U)[] ? U : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number
// Promise 반환 타입 탈기
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Result = UnwrapPromise<Promise<string>>; // string
TypeScript
복사
Template Literal Types
type EventName = 'click' | 'focus' | 'blur';
type HandlerName = `on${Capitalize<EventName>}`;
// 'onClick' | 'onFocus' | 'onBlur'
// API 엔드포인트 타입 생성
type HttpMethod = 'get' | 'post' | 'put' | 'delete';
type ApiPath = '/users' | '/orders' | '/products';
type ApiEndpoint = `${Uppercase<HttpMethod>} ${ApiPath}`;
// 'GET /users' | 'POST /users' | ... | 'DELETE /products'
TypeScript
복사
13. 실무 패턴
React 컴포넌트 Props 타입
interface ButtonProps {
label: string;
variant?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
onClick: () => void;
}
const Button: React.FC<ButtonProps> = ({
label,
variant = 'primary',
size = 'md',
disabled = false,
onClick,
}) => (
<button
className={`btn btn-${variant} btn-${size}`}
disabled={disabled}
onClick={onClick}
>
{label}
</button>
);
// 제네릭 컴포넌트
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map(item => (
<li key={keyExtractor(item)}>{renderItem(item)}</li>
))}
</ul>
);
}
TypeScript
복사
타입 안전한 API 호출
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
async function fetchApi<T>(url: string): Promise<ApiResponse<T>> {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
return res.json() as Promise<ApiResponse<T>>;
}
// 반환 타입이 자동 추론됨
const { data: user } = await fetchApi<User>('/api/users/1');
console.log(user.name); // string
// Zod를 활용한 런타임 검증 (실무 최강 패턴)
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
type User = z.infer<typeof UserSchema>; // 컴파일 타입 자동 생성
const parsed = UserSchema.parse(await res.json()); // 런타임 검증
TypeScript
복사
정리
주제 | 핵심 |
기본 타입 | string, number, boolean, null, undefined, unknown, never |
객체 형태 | interface (OOP) vs type (복합 타입) |
연산 | Union(`\ |
제네릭 | 재사용 가능한 타입, extends 제약, keyof |
유틸리티 타입 | Partial, Pick, Omit, Record, ReturnType |
Narrowing | typeof, instanceof, in, 커스텀 타입 가드 |
고급 타입 | Mapped, Conditional, Template Literal |
TypeScript는 타입을 얼마나 올바르게 설계하느냐에 따라 컴파일러가 오류를 얼마나 잊어주는지가 결정됨. any 없이도 충분한 코드를 작성하는 습관을 들이는 게 포인트.

