TypeScript 개요 및 기본 설정
TypeScript는 Microsoft에서 개발한 JavaScript의 슈퍼셋으로, 정적 타이핑을 지원하여 대규모 애플리케이션 개발을 더욱 안정적으로 만들어줍니다.
설치 방법:
- 전역 설치:
npm install -g typescript - 프로젝트별 설치:
npm install --save-dev typescript
기본 명령어:
tsc -h- 도움말 확인tsc --init- tsconfig.json 설정 파일 생성tsc 파일명.ts- 단일 파일 컴파일tsc- tsconfig.json 설정 기반 전체 컴파일
tsconfig.json 주요 설정:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
기본 타입 시스템
TypeScript는 JavaScript의 모든 타입을 지원하며, 추가적인 타입 시스템을 제공합니다.
기본 타입들:
// 숫자 타입
let price: number = 99.99;
let count: number = 100;
// 문자열 타입
let name: string = "홍길동";
let greeting: string = `안녕하세요, ${name}님!`;
// 불리언 타입
let isActive: boolean = true;
// 배열 타입 (두 가지 표기법)
let numbers: number[] = [1, 2, 3];
let items: Array<string> = ["사과", "바나나"];
// 튜플 타입
let userInfo: [string, number, boolean] = ["김철수", 25, true];
// 열거형
enum Status {
Pending = "PENDING",
Approved = "APPROVED",
Rejected = "REJECTED"
}
let currentStatus: Status = Status.Approved;
특수 타입들:
// any 타입 - 타입 검사 우회
let flexible: any = { name: "임의" };
flexible = 123;
// void 타입 - 반환값 없음
function logMessage(msg: string): void {
console.log(msg);
}
// never 타입 - 절대 반환하지 않음
function throwError(message: string): never {
throw new Error(message);
}
// null과 undefined
let emptyValue: null = null;
let notDefined: undefined = undefined;
타입 단언:
// 두 가지 방식
let data: any = "이것은 문자열입니다";
// 꺽쇠 괄호 방식
let length1: number = (<string>data).length;
// as 키워드 방식 (JSX와 호환)
let length2: number = (data as string).length;
인터페이스와 타입
인터페이스는 객체의 구조를 정의하는 강력한 방법입니다.
기본 인터페이스:
interface User {
id: number;
name: string;
email?: string; // 선택적 속성
readonly createdAt: Date; // 읽기 전용 속성
}
const user: User = {
id: 1,
name: "박지성",
createdAt: new Date()
};
함수 타입 인터페이스:
interface SearchFunction {
(query: string, page: number): string[];
}
const searchProducts: SearchFunction = function(q, p) {
return [`제품${q}_${p}`];
};
인덱서블 타입:
interface StringMap {
[key: string]: string;
}
const translations: StringMap = {
hello: "안녕하세요",
goodbye: "안녕히 가세요"
};
인터페이스 확장:
interface Employee extends User {
department: string;
salary: number;
}
const employee: Employee = {
id: 2,
name: "이영희",
department: "개발팀",
salary: 5000,
createdAt: new Date()
};
클래스와 객체 지향 프로그래밍
TypeScript는 완전한 클래스 기반 객체 지향 프로그래밍을 지원합니다.
기본 클래스 정의:
class Vehicle {
protected brand: string;
private mileage: number = 0;
constructor(brand: string) {
this.brand = brand;
}
public drive(distance: number): void {
this.mileage += distance;
console.log(`${this.brand}가 ${distance}km 주행했습니다.`);
}
public getMileage(): number {
return this.mileage;
}
}
class Car extends Vehicle {
private model: string;
constructor(brand: string, model: string) {
super(brand);
this.model = model;
}
public showDetails(): void {
console.log(`차종: ${this.brand} ${this.model}`);
}
}
접근자(Getter/Setter):
class Product {
private _price: number = 0;
get price(): number {
return this._price;
}
set price(newPrice: number) {
if (newPrice > 0) {
this._price = newPrice;
} else {
console.log("가격은 0보다 커야 합니다.");
}
}
}
const product = new Product();
product.price = 1000;
console.log(product.price);
추상 클래스:
abstract class Shape {
abstract calculateArea(): number;
display(): void {
console.log(`면적: ${this.calculateArea()}`);
}
}
class Rectangle extends Shape {
constructor(private width: number, private height: number) {
super();
}
calculateArea(): number {
return this.width * this.height;
}
}
함수 타입과 고급 기능
TypeScript에서 함수는 더 엄격한 타입 검사와 고급 기능을 제공합니다.
함수 타입 정의:
// 함수 선언
function addNumbers(a: number, b: number): number {
return a + b;
}
// 함수 표현식
const multiply = (a: number, b: number): number => a * b;
// 함수 타입 별칭
type MathOperation = (a: number, b: number) => number;
const divide: MathOperation = (a, b) => a / b;
선택적 매개변수와 기본값:
function createUser(
name: string,
age: number = 20,
role?: string
): { name: string; age: number; role?: string } {
return { name, age, role };
}
const user1 = createUser("김민준"); // { name: "김민준", age: 20 }
const user2 = createUser("이서연", 30, "관리자");
나머지 매개변수:
function formatMessage(template: string, ...values: any[]): string {
return template.replace(/{(\d+)}/g, (match, index) => {
return values[index] || match;
});
}
console.log(formatMessage("{0}님, {1}에 오신 것을 환영합니다!", "손님", "2024년"));
함수 오버로딩:
function processInput(input: string): string;
function processInput(input: number): number;
function processInput(input: boolean): boolean;
function processInput(input: string | number | boolean): string | number | boolean {
if (typeof input === "string") {
return input.toUpperCase();
} else if (typeof input === "number") {
return input * 2;
} else {
return !input;
}
}
console.log(processInput("hello")); // "HELLO"
console.log(processInput(5)); // 10
console.log(processInput(true)); // false
제네릭 프로그래밍
제네릭은 타입을 매개변수처럼 사용하여 재사용 가능한 코드를 작성할 수 있게 합니다.
제네릭 함수:
function createArray<T>(length: number, value: T): T[] {
return Array(length).fill(value);
}
const numberArray = createArray(3, 0); // number[]
const stringArray = createArray(3, "default"); // string[]
function identity<T>(arg: T): T {
return arg;
}
const result = identity<string>("제네릭 테스트");
제네릭 인터페이스:
interface Repository<T> {
findById(id: number): T | null;
save(entity: T): T;
findAll(): T[];
}
class UserRepository implements Repository<User> {
private users: User[] = [];
findById(id: number): User | null {
return this.users.find(u => u.id === id) || null;
}
save(user: User): User {
this.users.push(user);
return user;
}
findAll(): User[] {
return this.users;
}
}
제네릭 제약 조건:
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): void {
console.log(`길이: ${arg.length}`);
}
logLength("문자열"); // 가능
logLength([1, 2, 3]); // 가능
// logLength(123); // 에러: 숫자는 length 속성이 없음
keyof 제약 조건:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: "홍길동", age: 30 };
console.log(getProperty(person, "name")); // "홍길동"
// console.log(getProperty(person, "email")); // 에러: email 속성 없음
타입 조작과 고급 타입
TypeScript는 타입을 조작하고 결합하는 다양한 고급 기능을 제공합니다.
유니언 타입과 교차 타입:
// 유니언 타입 (OR)
type ID = string | number;
let userId: ID = "user123";
userId = 456; // 둘 다 가능
// 교차 타입 (AND)
interface Person {
name: string;
}
interface Employee {
id: number;
department: string;
}
type EmployeePerson = Person & Employee;
const employee: EmployeePerson = {
name: "김사원",
id: 100,
department: "개발팀"
};
타입 가드:
function processValue(value: string | number): string {
if (typeof value === "string") {
return value.toUpperCase(); // 여기서 value는 string 타입
} else {
return value.toFixed(2); // 여기서 value는 number 타입
}
}
// 사용자 정의 타입 가드
interface Cat {
meow(): void;
}
interface Dog {
bark(): void;
}
function isCat(animal: Cat | Dog): animal is Cat {
return (animal as Cat).meow !== undefined;
}
매핑 타입:
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type Optional<T> = {
[K in keyof T]?: T[K];
};
interface Todo {
title: string;
description: string;
completed: boolean;
}
type ReadonlyTodo = Readonly<Todo>;
type PartialTodo = Optional<Todo>;
조건부 타입:
type NonNullable<T> = T extends null | undefined ? never : T;
type ExtractType<T, U> = T extends U ? T : never;
type Test1 = NonNullable<string | null>; // string
type Test2 = ExtractType<string | number, string>; // string
열거형(Enums) 심화
TypeScript의 열거형은 더 구조化的된 상수 집합을 정의할 수 있습니다.
숫자 열거형:
enum Direction {
North, // 0
South, // 1
East, // 2
West // 3
}
// 값 초기화
enum StatusCode {
Success = 200,
NotFound = 404,
Error = 500
}
// 역 매핑 지원
console.log(Direction.North); // 0
console.log(Direction[0]); // "North"
문자열 열거형:
enum LogLevel {
ERROR = "ERROR",
WARN = "WARN",
INFO = "INFO",
DEBUG = "DEBUG"
}
function log(level: LogLevel, message: string): void {
console.log(`[${level}]: ${message}`);
}
log(LogLevel.ERROR, "오류 발생!");
상수 열거형:
const enum Colors {
Red = "#FF0000",
Green = "#00FF00",
Blue = "#0000FF"
}
// 컴파일 시 값으로 치환됨
let backgroundColor = Colors.Blue;
모듈과 네임스페이스
대규모 애플리케이션에서 코드를 구성하는 방법입니다.
ES6 모듈:
// mathUtils.ts
export const PI = 3.14159;
export function calculateCircleArea(radius: number): number {
return PI * radius * radius;
}
export class Calculator {
add(a: number, b: number): number {
return a + b;
}
}
// main.ts
import { PI, calculateCircleArea, Calculator } from "./mathUtils";
console.log(PI);
console.log(calculateCircleArea(5));
const calc = new Calculator();
console.log(calc.add(10, 20));
네임스페이스:
namespace MyApplication {
export interface Config {
apiUrl: string;
timeout: number;
}
export class ApiClient {
constructor(private config: Config) {}
request(endpoint: string): void {
console.log(`Requesting ${this.config.apiUrl}/${endpoint}`);
}
}
}
// 사용
const client = new MyApplication.ApiClient({
apiUrl: "https://api.example.com",
timeout: 5000
});
client.request("/users");
타입 호환성
TypeScript의 구조적 타이핑 시스템 이해하기.
구조적 타이핑:
interface Point {
x: number;
y: number;
}
interface Point3D {
x: number;
y: number;
z: number;
}
let point2D: Point = { x: 0, y: 0 };
let point3D: Point3D = { x: 0, y: 0, z: 0 };
// Point3D는 Point 호환 (추가 속성 허용)
point2D = point3D; // 가능
// point3D = point2D; // 에러: z 속성 없음
함수 호환성:
let fn1 = (a: number, b: number) => a + b;
let fn2 = (a: number) => a;
// fn2는 fn1 호환 (매개변수 수 적음 허용)
fn1 = fn2; // 가능
// fn2 = fn1; // 에러: 매개변수 수 불일치
제네릭 호환성:
interface Empty<T> {}
let x: Empty<number>;
let y: Empty<string>;
x = y; // 가능: 구조가 동일
디코레이터와 메타데이터
메타 프로그래밍을 위한 고급 기능입니다.
클래스 디코레이터:
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
메서드 디코레이터:
function log(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`호출: ${propertyName} with args: ${JSON.stringify(args)}`);
const result = method.apply(this, args);
console.log(`결과: ${result}`);
return result;
};
return descriptor;
}
class Calculator {
@log
multiply(a: number, b: number): number {
return a * b;
}
}
선언 병합
기존 타입을 확장하는 방법입니다.
인터페이스 병합:
interface User {
name: string;
}
interface User {
age: number;
}
// 두 인터페이스가 병합됨
const user: User = {
name: "홍길동",
age: 30
};
네임스페이스 병합:
namespace Validation {
export const isEmailValid = (email: string): boolean => {
return email.includes("@");
};
}
namespace Validation {
export const isPhoneValid = (phone: string): boolean => {
return /^\d{3}-\d{4}-\d{4}$/.test(phone);
};
}
// Validation.isEmailValid와 Validation.isPhoneValid 모두 사용 가능
실용적인 팁과 모범 사례
1. 엄격 모드 사용:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true
}
}
2. 타입 좁히기 활용:
function process(data: string | number | null): void {
if (data === null) {
console.log("데이터 없음");
return;
}
if (typeof data === "string") {
console.log(data.toUpperCase());
} else {
console.log(data.toFixed(2));
}
}
3. 유틸리티 타입 활용:
interface UserProfile {
id: number;
name: string;
email: string;
age: number;
address: {
street: string;
city: string;
};
}
// 필요한 속성만 선택
type UserSummary = Pick<UserProfile, "id" | "name">;
// 특정 속성 제외
type UserWithoutAddress = Omit<UserProfile, "address">;
4. 타입 추론 활용:
// 명시적 타입보다 추론에 의존
const products = [
{ id: 1, name: "노트북", price: 1000 },
{ id: 2, name: "마우스", price: 50 }
]; // 타입이 자동으로 추출됨
type Product = typeof products[number]; // 타입 별칭으로 재사용
JavaScript 상식 보충
변수 호이스팅:
console.log(x); // undefined (에러 아님)
var x = 5;
// 실제 동작:
var x; // 선언이 호이스팅됨
console.log(x);
x = 5;
함수 호이스팅:
hoisted(); // "호이스팅된 함수"
function hoisted() {
console.log("호이스팅된 함수");
}
// 화살표 함수는 호이스팅되지 않음
// notHoisted(); // 에러
const notHoisted = () => console.log("호이스팅 안됨");
this 바인딩:
const obj = {
value: 42,
regularMethod: function() {
console.log(this.value); // this는 obj
},
arrowMethod: () => {
console.log(this.value); // this는 전역 객체
}
};
obj.regularMethod(); // 42
obj.arrowMethod(); // undefined
클로저:
function createCounter() {
let count = 0;
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
};
}
const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.getCount()); // 2