TypeScript를 싫어하는 JavaScript 개발자를 위한 15분 안에 배우는 TypeScript

발행: (2025년 12월 16일 오전 10:06 GMT+9)
9 min read
원문: Dev.to

Source: Dev.to

TypeScript vs JavaScript

그것은 의도와 규율, 그리고 증거가 있는 JavaScript입니다.

핵심적으로 TypeScript는 단순히 JavaScript에 typesgenerics를 더한 것일 뿐—그 이상도, 마법 같은 것도 없습니다. 새로운 런타임 동작을 추가하지 않으며, 코드를 작성할 때 자신(또는 팀원)에게 거짓말을 하지 않도록 보장해 줍니다.

이미 JavaScript를 알고 있다면, TypeScript에서 실제로 중요한 것이 무엇인지 가장 빠르게 이해할 수 있는 방법입니다.

JavaScript vs. TypeScript: 타입에 관한 진실

TypeScript는 새로운 원시 데이터 타입을 도입하지 않습니다.
이미 알고 있는 모든 것이 여전히 존재합니다:

  • string
  • number
  • boolean
  • null
  • undefined
  • symbol
  • bigint

TypeScript의 역할은 발명이 아니라 강제입니다.

  • JavaScript: “런타임에 알아낼 겁니다.”
  • TypeScript: “실행하기 전에 증명하세요.”

Methods Are the Same — Safety Is Not

All your familiar methods still exist:

  • Stringsconcat, slice, charAt
  • Arraysmap, filter, reduce, find

The difference is that TypeScript knows what you’re allowed to call.

let nums: number[] = [1, 2, 3];
nums.map(n => n * 2);               // ✅ OK
nums.map(n => n.toUpperCase());     // ❌ Error – caught at compile time

JavaScript would let the second line fail at runtime; TypeScript stops it at compile time. That’s the entire value proposition.

타입 주석 vs. 타입 추론

명시적으로 주석을 달 수 있습니다:

let name: string = "Ram";

하지만 TypeScript는 더 똑똑합니다:

let name = "Ram"; // `string`으로 추론됨

TypeScript는 타입을 자동으로 추적합니다. 이것을 타입 추론이라고 하며, 변수 선언을 한 곳에서 하고 다른 곳에서 값을 변경하는 것이 보통 좋지 않은 이유입니다.

any 트랩 (이렇게 하지 마세요)

많은 사람들이 TypeScript를 “벗어나기” 위해 any를 사용합니다. 이는 TypeScript의 목적을 무색하게 합니다.

let value: any = 10;
value.toUpperCase(); // ✅ TS는 허용하지만 런타임에서는 오류 발생

그래서 tsconfig.jsonnoImplicitAny 옵션이 존재합니다. 코드 전역에 any를 남발한다면, 사실상 추가적인 단계만 더한 순수 JavaScript를 작성하는 것과 같습니다.

Source:

함수: 매개변수 반환도 중요합니다

TypeScript는 입력만 중요한 것이 아니라, 출력도 중요합니다.

function addTwo(num: number): number {
  return num + 2;
}

반환 타입을 명시하지 않으면 다음 코드도 컴파일되지만, 이는 버그가 됩니다:

function addTwo(num: number) {
  return "hello"; // ❌ 반환 타입을 선언하면 타입 오류 발생
}

TypeScript는 계약의 양쪽을 모두 고정시킬 수 있게 해줍니다.

특수 반환 타입

  • void → 함수가 부수 효과만 수행함
  • never → 함수가 절대 반환하지 않음 (예외를 발생시키거나 크래시)
function handleError(msg: string): void {
  console.log(msg);
}

function crash(msg: string): never {
  throw new Error(msg);
}

팀에서 반환 타입이 중요한 이유

잘 타입이 지정된 함수는 본문을 읽지 않아도 이야기를 전달한다:

function getCourse(): { title: string; price: number } {
  return { title: "TypeScript Mastery", price: 499 };
}

누구든지 즉시 확인할 수 있습니다:

  • 무엇을 반환하는지
  • 정확한 구조
  • 필수 항목이 무엇인지

이는 단순한 문법을 넘어 팀 커뮤니케이션입니다.

Type Aliases: Naming Shapes

Type aliases를 사용하면 의도에 이름을 부여할 수 있습니다.

type User = {
  name: string;
  email: string;
  isActive: boolean;
};

JavaScript는 “trust me.”라고 말합니다.
TypeScript는 “prove it.”라고 말합니다.

readonly, 선택적, 그리고 유니온 타입

readonly

readonly _id: string;

읽을 수는 있지만, 변경할 수는 없습니다.

선택적 속성

creditCard?: number;

속성이 존재할 수도 있고 없을 수도 있습니다; TypeScript는 이를 확인하도록 강제합니다.

유니온 타입

let id: number | string;

다음과 같은 경우에 많이 사용됩니다:

  • 역할 기반 접근 제어 (RBAC)
  • API 응답
  • 조건부 흐름

튜플: 제어된 혼돈

튜플은 순서가 정해진, 타입이 지정된 배열입니다:

let user: [string, number, boolean];
  • 이것은 TypeScript에서만 존재하며, 컴파일된 JavaScript는 신경 쓰지 않습니다.
  • push()도 여전히 동작하지만 (가능하면 적게 사용하세요).
  • 순서가 정말 중요한 경우에만 사용하세요.

열거형: 명명된 상수

const enum SeatChoice {
  aisle = 10,
  middle,
  window
}
  • 가독성이 좋고 예측 가능함.
  • 열거형이 컴파일 시 인라인되므로 런타임 모호성이 없음.

Source:

인터페이스 vs. 타입 (짧은 버전)

인터페이스

  • 확장 가능.
  • 클래스와 아름답게 작동하며 OOP 계약을 강제합니다.
interface IUser {
  email: string;
  startTrial(): string;
}
  • 인터페이스는 재오픈(선언 병합)할 수 있습니다.
  • 타입은 재오픈할 수 없습니다.

타입

  • 유니언, 인터섹션 및 원시 별칭에 유용합니다.
  • 병합할 수 없지만 일회성 형태에 적합합니다.

Classes, Constructors, and Access Modifiers

TypeScript는 OOP를 덜 고통스럽게 만들어 줍니다:

class User {
  constructor(
    public email: string,
    public name: string,
    private userId: string
  ) {}
}

Access Modifiers

ModifierVisibility
publicAnywhere
privateInside the class only
protectedInside the class and its subclasses

이러한 수정자는 실제 시스템에서 “접근 혼란”을 방지하는 데 도움이 됩니다.

인터페이스 + 클래스 = 구조적 무결성

인터페이스 (형태)

interface TakePhoto {
  cameraMode: string;
  filter: string;
}

클래스 (동작)

class Camera implements TakePhoto {
  cameraMode = "auto";
  filter = "none";

  snap() { /* … */ }
}

인터페이스존재해야 할 것을 정의하고; 클래스구현을 제공합니다.
이 패턴은 백엔드(및 프론트엔드) 시스템에서 매우 잘 확장됩니다.

추상 클래스: 인스턴스화 없이 의도

abstract class TakePhoto {
  abstract getSepia(): void;
}
  • 직접 인스턴스화할 수 없습니다.
  • 서브클래스는 반드시 추상 멤버를 구현해야 하며, 이를 통해 필요한 동작을 보장합니다.

Generics: The Real Power Move

Generics let you write logic once – safely:

function identity<T>(val: T): T {
  return val;
}

Common places where generics shine:

  • API client wrappers
  • Repository patterns
  • Response models

They’re not an OOP replacement; they’re an evolution of type safety.

타입 좁히기: 런타임 안전성

TypeScript는 절대 “추측”하지 않습니다 – 타입을 명시적으로 좁히는 방법은 다음과 같습니다:

  • typeof 체크
  • instanceof 체크
  • in 연산자

이러한 기술은 컴파일러를 만족시키면서 리플렉션 없이 올바른 런타임 동작을 보장합니다.

Discriminated Unions (Cleanest Pattern)

interface CreditCard {
  paymentType: "card";
  cardNumber: string;
}

interface PayPal {
  paymentType: "paypal";
  email: string;
}

type PaymentMethod = CreditCard | PayPal;
  • 단일 구분 필드(paymentType)는 모호함이 전혀 없습니다.
  • switch 문이나 전체를 포괄하는 if 검사에 완벽합니다.

Conclusion

  • TypeScript는 당신을 느리게 하지 않는다 – 불명확한 코드가 그렇다.
  • 모호함을 불법으로 만들어 명시적이고 유지보수 가능하며 타입‑안전한 코드를 작성하도록 강제한다.
Back to Blog

관련 글

더 보기 »

JSDoc은 TypeScript이다

2023년 5월, Svelte 저장소의 내부 리팩토링 PR https://github.com/sveltejs/svelte/pull/8569가 Hacker News의 메인 페이지에 올랐습니다. 겉보기에…