1. TypeScript란?
TypeScript는 JavaScript에 정적 타입을 추가한 언어입니다. 대규모 애플리케이션 개발 시 발생할 수 있는 런타임 에러를 컴파일 시점에 미리 방지하여 개발 생산성을 비약적으로 높여줍니다.
2. 설치와 설정 (Node.js & tsc & VS Code)
TypeScript는 브라우저나 Node.js 환경에서 직접 실행될 수 없으므로, 코드를 JavaScript로 변환해주는 **TypeScript 컴파일러(tsc)** 설치가 필요합니다.
2.1 Node.js 및 tsc 설치
TypeScript를 구성하기 위해서는 먼저 패키지 매니저(npm)를 갖추기 위한 Node.js가 필수적입니다.
- Node.js 설치: JavaScript 가이드를 참고하여 본인의 OS(Windows/macOS/Linux)에 맞게 Node.js를 먼저 설치합니다.
- TypeScript 컴파일러 글로벌 설치: 터미널을 실행하여 시스템 전역에서 컴파일러를 실행할 수 있도록 패키지를 설치합니다.
npm install -g typescript ts-nodebash(
ts-node는 컴파일 과정을 거치지 않고 메모리 상에서 TypeScript를 즉시 실행할 수 있게 해주는 아주 유용한 라이브러리입니다.) - 설치 확인: 아래 명령어를 쳐서 정상 설치 유무를 확인합니다.
tsc -v ts-node -vbash
2.2 TypeScript 설정 파일 (tsconfig.json) 생성
TypeScript 컴파일러의 작동 방식과 엄격도를 세부 정의하는 tsconfig.json 파일을 프로젝트 루트 폴더에 생성해야 합니다.
- 프로젝트 폴더를 열고 터미널(
Ctrl + `)에서 아래 명령어를 실행하여 기본 설정 파일을 자동 빌드합니다.tsc --initbash - 생성된
tsconfig.json에서 가장 기본적으로 추천하는 세부 핵심 옵션 세팅입니다.{ "compilerOptions": { "target": "ES2022", // 컴파일될 자바스크립트의 표준 버전 지정 "module": "CommonJS", // 모듈 시스템 방식 지정 "outDir": "./dist", // 컴파일된 JS 파일들이 저장될 아웃풋 폴더 지정 "strict": true, // 강력한 모든 타입 검사 규칙 일괄 활성화 (매우 권장) "esModuleInterop": true // ES6 모듈 사양과 호환성 향상 } }json
3. tsconfig.json 설정 커스터마이징 (중급)
엔터프라이즈 환경 및 협업 규모의 코드 생산성 확보를 위해, tsconfig.json의 고급 속성들을 커스터마이징하여 프로젝트에 특화된 컴파일 환경을 구성해야 합니다.
1) 엄격한 검사 옵션 세부 튜닝
"strict": true: 아래의 모든 개별 엄격 옵션을 일괄 활성화합니다."strictNullChecks": true:null및undefined값을 명시적으로 처리하도록 강제하여Cannot read property of undefined에러를 원천 차단합니다."noImplicitAny": true: 타입 추론이 불가능한 매개변수나 변수에any타입을 암시적으로 부여하는 것을 금지하며, 반드시 개발자가 명시적 타입을 작성하게 합니다.
2) 경로 별칭 (Path Aliases) 설정
상대 경로(../../../components/Button) 대신 깔끔한 절대 경로(@/components/Button)를 사용할 수 있도록 모듈 해상도를 변경합니다.
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}json
3) JSON 및 빌드 모듈 설정
"resolveJsonModule": true: 외부.json설정 파일들을 직접 import하여 정적 타입 검사를 통과하게 합니다."skipLibCheck": true:node_modules내부의 외부d.ts라이브러리 타입 선언 검사를 스킵하여 빌드 속도를 획기적으로 향상시킵니다.
4. 기본 타입
TypeScript가 제공하는 타입들을 명시적으로 선언하면 컴파일 단계에서 오류를 사전에 차단할 수 있습니다.
// ── 원시 타입 ────────────────────────────────────
let name: string = 'TypeScript';
let age: number = 10;
let flag: boolean = true;
let empty: null = null;
let undef: undefined = undefined;
// ── 배열 & 튜플 ──────────────────────────────────
let nums: number[] = [1, 2, 3];
let strNums: Array<string> = ['a', 'b'];
// 튜플: 각 위치의 타입이 고정된 배열
let point: [number, number] = [10, 20];
let entry: [string, number, boolean] = ['id', 1, true];
// ── 유니온 타입 ──────────────────────────────────
let id: string | number = 'abc-123';
id = 42; // 정상
function format(val: string | number): string {
return typeof val === 'string' ? val.toUpperCase() : val.toFixed(2);
}
// ── 리터럴 타입 (값 자체를 타입으로) ──────────────
type Direction = 'up' | 'down' | 'left' | 'right';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type HttpStatus = 200 | 201 | 400 | 401 | 404 | 500;
let move: Direction = 'up'; // 'up'|'down'|'left'|'right' 외 타입 오류
// ── 특수 타입 ────────────────────────────────────
let anything: any = 42; // 타입 검사 완전 비활성 (사용 지양)
let safe: unknown = 42; // 타입 검사 전 사용 불가 → 좁히기 필요
let neverEnd: never; // 절대 발생하지 않는 값 (exhaustive check)
function throwErr(msg: string): never { throw new Error(msg); }
// ── 열거형 (Enum) ────────────────────────────────
enum Status { Pending = 'PENDING', Active = 'ACTIVE', Inactive = 'INACTIVE' }
const s: Status = Status.Active; // 'ACTIVE'typescript
any는 타입 검사를 완전히 포기하므로 지양하세요. unknown은 타입을 좁히기(narrowing) 전까지 사용을 막아 안전합니다. 외부 API 응답처럼 실제 타입을 모를 때 unknown을 쓰는 것이 모범 사례입니다.
5. 인터페이스
인터페이스(Interface)는 객체의 구조(형태)를 정의하는 타입 계약입니다. 클래스·함수 파라미터·API 응답 타입 정의에 광범위하게 사용됩니다.
// ── 기본 인터페이스 ──────────────────────────────
interface User {
readonly id: number; // 읽기 전용
name: string;
email?: string; // 선택적 속성
age: number;
}
const user: User = { id: 1, name: '철수', age: 28 };
// user.id = 2; // Error: readonly 속성 변경 불가
// ── 인터페이스 확장 (extends) ─────────────────────
interface Admin extends User {
role: 'super' | 'manager';
permissions: string[];
}
const admin: Admin = {
id: 1, name: '영희', age: 30,
role: 'super',
permissions: ['read', 'write', 'delete'],
};
// ── 다중 확장 ─────────────────────────────────────
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
interface Post extends User, Timestamped {
title: string;
content: string;
}typescript
함수 시그니처 & 인덱스 시그니처
// 함수 타입 정의
interface Formatter {
(value: string): string;
prefix: string; // 추가 프로퍼티도 정의 가능
}
// 인덱스 시그니처: 동적 키를 허용
interface StringMap { [key: string]: string; }
interface NumberMap { [key: string]: number; }
const env: StringMap = { NODE_ENV: 'production', PORT: '3000' };
// 선택적 메서드 포함
interface Logger {
log(msg: string): void;
error(msg: string, err?: Error): void;
debug?(msg: string): void; // 선택적 메서드
}typescript
6. 타입 별칭 (Type Aliases)
타입 별칭은 특정 타입에 새로운 이름을 부여하는 기능입니다. interface와 유사하지만, 원시 타입, 유니온 타입, 튜플 등 더 복잡하거나 다양한 타입에 이름을 붙일 때 유용하게 사용할 수 있습니다.
// 1. 객체 타입 정의
type Point = {
x: number;
y: number;
};
// 2. 유니온 타입 정의 (가장 핵심적인 용도)
type Status = 'pending' | 'approved' | 'rejected';
type ID = string | number;
const currentStatus: Status = 'pending';
const userId: ID = 101;typescript
일반적인 객체 타입을 정의할 때는 확장(extends)이 가능한
interface를 우선적으로 사용하고, 유니온 타입이나 교차 타입, 원시 타입 등을 조합할 때는 type을 사용하는 것이 모던 타입스크립트의 표준 규칙입니다.
7. 제네릭 (Generics)
제네릭은 타입을 함수의 매개변수처럼 동적으로 입력받아 재사용 가능한 컴포넌트를 타입 안전하게 작성하는 핵심 기법입니다.
// ── 기본 제네릭 함수 ─────────────────────────────
function identity<T>(arg: T): T { return arg; }
identity<string>("Hello"); // 명시적 지정
identity(100); // 타입 추론: T = number
// ── 실용 예시: first / last ────────────────────────
function first<T>(arr: T[]): T | undefined { return arr[0]; }
function last<T>(arr: T[]): T | undefined { return arr[arr.length - 1]; }
console.log(first([1, 2, 3])); // 1
console.log(last(['a', 'b'])); // 'b'typescript
제네릭 제약 (extends)
// T는 반드시 length 속성을 가져야 함
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest('hello', 'hi'); // 'hello'
longest([1, 2, 3], [1, 2]); // [1, 2, 3]
// longest(1, 2); // Error: number에 length 없음
// keyof 제약: 객체의 실제 키만 허용
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: '철수', email: 'cs@test.com' };
getProperty(user, 'name'); // '철수'
// getProperty(user, 'age'); // Error: 'age'는 User의 키가 아님typescript
제네릭 인터페이스 & 클래스
// 제네릭 인터페이스: 타입 안전 API 응답 래퍼
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
interface Paginated<T> {
items: T[];
total: number;
page: number;
pageSize: number;
}
// 사용 예시
type UserListRes = ApiResponse<Paginated<User>>;
// 제네릭 클래스: 타입 안전 스택
class Stack<T> {
private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
peek(): T | undefined { return this.items.at(-1); }
isEmpty(): boolean { return this.items.length === 0; }
size(): number { return this.items.length; }
}
const numStack = new Stack<number>();
numStack.push(1);
numStack.push(2);
console.log(numStack.pop()); // 2typescript
제네릭 유틸리티 함수 패턴
// 배열을 Map으로 변환
function keyBy<T, K extends keyof T>(arr: T[], key: K): Map<T[K], T> {
return new Map(arr.map(item => [item[key], item]));
}
const users = [
{ id: 1, name: '철수' },
{ id: 2, name: '영희' },
];
const userMap = keyBy(users, 'id');
userMap.get(1); // { id: 1, name: '철수' }
// 깊은 Readonly
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
// 비동기 함수의 반환 타입 추출
type Awaited<T> = T extends Promise<infer U> ? U : T;
type Result = Awaited<Promise<string>>; // stringtypescript
8. 유틸리티 타입 (Utility Types)
TypeScript는 기존 정의된 타입을 변형하여 새로운 타입을 쉽게 도출할 수 있도록 여러 유용한 유틸리티 타입을 전역으로 내장하여 제공합니다.
1. Partial<T>
대상 타입 T의 모든 프로퍼티를 선택적(Optional, ?) 속성으로 변환한 새로운 타입을 만듭니다.
interface User {
name: string;
age: number;
}
// 모든 속성이 선택사항이 됨 ({ name?: string; age?: number; })
const updateProfile = (id: number, fieldsToUpdate: Partial<User>) => {
// ...
};typescript
2. Pick<T, K>
대상 타입 T에서 특정 몇 개의 키(K)들만 직접 선택해서 쏙 골라낸 서브타입을 추출합니다.
interface Article {
id: number;
title: string;
content: string;
author: string;
}
// id와 title만 쏙 골라내어 구성
type ArticlePreview = Pick<Article, 'id' | 'title'>;
const preview: ArticlePreview = {
id: 1,
title: '모던 타입스크립트 기초 정리'
};typescript
3. Omit<T, K>
Pick의 반대로, 대상 타입 T에서 특정 키(K)들만 제외(생략)한 나머지 속성들로 구성된 타입을 도출합니다.
interface Product {
id: number;
name: string;
price: number;
category: string;
}
// id를 제외한 나머지 속성으로만 구성
type NewProduct = Omit<Product, 'id'>;
const item: NewProduct = {
name: '키보드',
price: 45000,
category: '전자기기'
};typescript
4. Readonly<T>
대상 타입 T의 모든 속성을 읽기 전용(Read-only)으로 바꾸어, 한 번 대입한 값의 속성을 재할당하려 할 때 컴파일 오류를 뿜어내도록 보호합니다.
interface Config {
apiKey: string;
}
const settings: Readonly<Config> = {
apiKey: 'XYZ123'
};
// Error: Cannot assign to 'apiKey' because it is a read-only property.
// settings.apiKey = 'NEW_KEY';typescript
9. 고급 타입 (keyof, typeof, 조건부 타입) (중급)
단순한 인터페이스 및 타입 정의 단계를 넘어, TypeScript의 정적 타입 시스템 연산을 활용하여 기존 코드를 정밀하게 매핑하고 동적인 타입 추론을 극한으로 유도하는 중급 핵심 테크닉입니다.
1) typeof 연산자
실제 자바스크립트 변수나 객체의 선언 형태를 정적 타입 명세로 그대로 추출해 줍니다.
const themeConfig = {
primary: "#2563eb",
darkBackground: "#0f172a",
fontSize: "14px"
};
// themeConfig 객체의 형태를 바탕으로 { primary: string; darkBackground: string; fontSize: string; } 타입 자동 빌드
type Theme = typeof themeConfig;typescript
2) keyof 연산자
객체 타입의 모든 프로퍼티 키들을 문자열 리터럴 유니온(Union) 타입으로 일괄 쏙 빼냅니다.
interface Car {
brand: string;
model: string;
year: number;
}
// 'brand' | 'model' | 'year' 타입 생성
type CarKey = keyof Car;
const key: CarKey = 'brand'; // 'brand', 'model', 'year' 외에 다른 값은 불가typescript
3) 조건부 타입 (Conditional Types)
타입 시스템 내부에서 삼항 연산자(extends ? :)를 수행하여 입력받은 타입 인자에 따라 다른 최종 타입을 동적으로 결정합니다.
// 제네릭 T가 string이면 string 배열 반환, 아니면 number 배열 반환
type ArrayType<T> = T extends string ? string[] : number[];
type TextArray = ArrayType<string>; // string[]
type NumericArray = ArrayType<number>; // number[]typescript
4) 매핑된 타입 (Mapped Types)
[K in Keys] 루프 연산을 가동하여 기존 객체 속성을 일괄 변조 및 재생산하는 혁신적인 기법입니다.
type Optional<T> = {
[P in keyof T]?: T[P]; // 기존 타입의 모든 속성을 순회하며 선택형(?)으로 튜닝
};
interface Profile {
name: string;
age: number;
}
type OptionalProfile = Optional<Profile>; // { name?: string; age?: number; }typescript
10. React with TypeScript 셋업 (중급)
현대 웹 프런트엔드의 사실상의 업계 표준인 React와 TypeScript를 매끄럽게 연결하고, 빌드 병목을 부수기 위해 Vite를 기반으로 통합하는 방법 및 타입 명세 정석을 다룹니다.
10.1 Vite React-TS 프로젝트 즉시 생성
# 1. Vite로 초고속 React TypeScript 템플릿 프로젝트 설치 생성
npm create vite@latest my-react-app -- --template react-ts
# 2. 패키지 설치 및 실행
cd my-react-app
npm install
npm run devbash
10.2 컴포넌트와 Props 타이핑 정석
React 컴포넌트 개발 시, 과거에 널리 사용되던 React.FC 대신 **함수 매개변수 구조분해 할당과 interface 결합** 방식을 통해 디폴트 프로퍼티 오염을 예방하는 것이 정석입니다.
import React, { useState } from 'react';
// 1. Props 명세 인터페이스
interface ButtonProps {
label: string;
onClick: () => void;
color?: 'blue' | 'gray';
}
// 2. 컴포넌트 선언 및 Props 타이핑 (React.FC 생략 권장)
export const CustomButton = ({ label, onClick, color = 'blue' }: ButtonProps) => {
return (
<button
onClick={onClick}
style={{ backgroundColor: color === 'blue' ? '#2563eb' : '#64748b', color: '#fff' }}
>
{label}
</button>
);
};
// 3. 상태(useState)와 이벤트(Event) 타이핑
export const Counter = () => {
// 제네릭을 이용한 명시적 State 타이핑
const [count, setCount] = useState<number>(0);
const [inputValue, setInputValue] = useState<string>("");
// Input 이벤트 객체 타이핑
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
};
return (
<div>
<p>Count: {count}</p>
<input type="text" value={inputValue} onChange={handleInputChange} />
<CustomButton label="증가" onClick={() => setCount(count + 1)} />
</div>
);
};typescript
11. 추천 학습 경로 & 다음 단계 (Recommended Path & Next Steps)
타입스크립트의 중급 설계 기술까지 이해하셨다면, 아래의 정밀한 추천 학습 경로를 밟아 프로덕션 수준의 웹 아키텍트이자 타입 세이프(Type-Safe) 풀스택 마스터로 거듭나세요!
[1단계] TypeScript 기초 문법 (타입 지정, 인터페이스, 제네릭 입문)
⬇️
[2단계] tsconfig 설정 커스터마이징 (strict 검사 고도화, 절대경로 path 맵핑)
⬇️
[3단계] React with TypeScript 셋업 (Vite 번들러 연계, 컴포넌트/Props 및 HTML 이벤트 타입 정합성 수립)
⬇️
[4단계] 고급 타입 학습 (typeof, keyof, 조건부 타입 extends 및 Mapped Types 연산 설계)