타입스크립트 왜 써야 할까?

kimminjunnn·2025년 4월 17일

CS

목록 보기
9/34

타입스크립트를 왜 쓰나요? (본인이 느낀점)

사실 내가 코딩을 시작할 때는 이미 TS 사용이 필수적인 분위기라,
필요에 의해 타입스크립트를 쓴 게 아니라
"왜 타입스크립트를 써야 하지?"에 대해 깊이 고민해보진 않았다.

그런데 문득 "만약 타입스크립트를 못 쓴다면?"이라는 가정을 해보니
몇 가지 불편한 점이 쉽게 떠올랐다.

1. 객체가 어떤 형식인지, 어떤 객체인지 바로 파악할 수 없을 것 같다.

// JavaScript 예시
function printUser(user) {
  console.log(user.name);
}

이 함수만 보면 user 객체에 name이 있다는 것만 알 수 있고,
그 외에 id, email 같은 속성이 있는지 없는지 전혀 알 수 없다.
게다가 타입이 없기 때문에 에디터 자동완성도 거의 안 되고,
잘못된 속성을 써도 에러 없이 실행돼서 디버깅도 늦어질 것 같다.

2. 함수에 어떤 인자를 넣어야 하는지 매번 구현부를 열어보거나 주석을 확인해야 할 것 같다.

// JS에서 다른 사람이 작성한 코드
function calculate(a, b, c) {
  return a + b * c;
}

a, b, c가 어떤 타입인지 모르면 사용할 때 항상 구현부를 열어봐야 한다.
숫자일 수도, 문자열일 수도 있고... 타입이 명확하지 않으면
호출부에서 실수하기 쉽고, 협업 시 피로도가 커진다.

3. 리팩토링할 때 타입이 보장되지 않으니 불안해서 마음껏 코드를 뜯어고치기 어려울 것 같다.

JS에서는 리팩토링 중 함수 이름을 바꾸거나 매개변수 순서를 바꾸면
그걸 어디서 사용했는지 하나하나 찾아야 한다.
IDE가 추적해주지 않거나, 추적해도 실수가 생기기 쉽다.

반면 TS는 타입이 연결되어 있어서 이름만 바꿔도
사용된 모든 곳에서 에러가 떠서 즉시 확인할 수 있다.

4. 협업 중 다른 개발자가 만든 함수나 컴포넌트를 쓸 때, 타입이 없다면 의존성도 많고 실수도 잦을 것 같다.

컴포넌트를 재사용하거나, 팀원이 만든 API 응답 데이터를 처리할 때
타입이 없으면 무조건 콘솔 찍어보거나 주석을 읽어야 한다.

반면 TS에서는 User, ApiResponse, Props 같은 이름만 봐도
데이터 형태를 바로 파악할 수 있고, 자동완성도 지원되기 때문에
협업 생산성이나 안정성이 크게 향상된다.


타입과 인터페이스의 차이를 아나요?

타입과 인터페이스의 개념

타입스크립트에서 타입과 인터페이스는 객체의 구조를 정의하는 두 가지 주요 방법입니다.

하지만 명확히 구분하여 사용하기 쉽지않은데 우리는 타입과 인터페이스의 개념과 차이점을 알아봅시다.

타입은 'type' 키워드를 사용하여 정의합니다. 예를 들어 다음과 같이 타입을 정의할 수 있습니다.

type User = {
  name: string;
  age: number;
};

인터페이스는 'interface' 키워드를 사용하여 정의합니다.

interface User {
  name: string;
  age: number;
}

두가지 다 모두 객체의 구조를 정의하는 데 사용가능하지만 그 사용목적과 기능에는 분명히 차이가 있을텐데
어떤 차이점이 있는 지 알아봅시다.

타입과 인터페이스의 차이점

  1. 타입정의가 가능한 것들
  2. 선언 병합 여부
  3. 확장

1. 타입과 인터페이스의 차이점: 타입 정의 가능 여부

type은 다양한 타입 정의가 가능하다

type은 단순한 객체 타입뿐만 아니라 다음과 같은 다양한 형태의 타입 정의가 가능하다:

  • 기본 타입 별칭
  • 유니언 타입
  • 튜플 타입
  • 리터럴 타입
  • 조건부 타입
  • 매핑된 타입
  • 유틸리티 타입 조합 등
type ID = string | number;           // 유니언,  여러 타입 중 하나만 허용할 수 있게 만든다.
type Tuple = [number, string];       // 튜플, 고정된 길이와 순서를 가지는 배열을 정의할 수 있다.
type Status = "success" | "error";   // 리터럴, 정해진 값들만 허용되도록 제한한다.

반면

❌ interface는 객체 타입과 함수 시그니처에만 제한됨
interface는 주로 객체의 구조를 정의하는 데 사용되며,
유니언, 튜플, 리터럴, 기본 타입 등은 정의할 수 없다.

interface User {
  name: string;
  age: number;
}

// ✅ 가능: 함수 타입 정의
interface Greet {
  (name: string): string;
}

// ❌ 불가능: 유니언 타입 정의
// interface ID = string | number; // 오류

2. 타입과 인터페이스의 차이점: 선언 병합(Declaration Merging)

interface는 선언 병합이 가능하다

interface같은 이름으로 여러 번 선언하면,
TypeScript가 자동으로 병합해준다.

interface User {
  name: string;
}

interface User {
  age: number;
}

// 결과적으로 아래와 같은 형태가 된다:
interface User {
  name: string;
  age: number;
}

이 특성 덕분에, 라이브러리 타입 확장이나
전역 선언 보강 같은 작업에 매우 유리하다.

반면

❌ type은 선언 병합이 불가능하다.
type은 같은 이름으로 중복 선언하려고 하면 에러가 발생한다.

ype User = {
  name: string;
}

type User = {
  age: number;
}

// ❌ 에러: Duplicate identifier 'User'

interface는 같은 이름으로 계속 쌓아가는 구조가 가능해서,
외부 라이브러리의 타입을 확장하거나 수정할 때 매우 유용하다!

3. 타입과 인터페이스의 차이점: 확장 방식 (확장성)

interfaceextends 키워드로 확장한다

interface는 다른 인터페이스를 extends 키워드로 상속해서 확장할 수 있다.
하나 이상의 인터페이스도 동시에 확장 가능하다.

interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

다중 상속도 가능하다.

interface A {
  a: string;
}
interface B {
  b: number;
}
interface C extends A, B {
  c: boolean;
}

반면

✅ type은 & 연산자(인터섹션)로 확장한다
type은 & 연산자를 사용해 기존 타입을 조합하여 새로운 타입을 만든다.

type Animal = {
  name: string;
};

type Dog = Animal & {
  breed: string;
};

interface처럼 계층 구조를 명시적으로 만들지는 않지만,
다양한 타입을 유연하게 합성할 수 있다는 장점이 있다.


🔧 타입 유틸리티란?

TypeScript에서는 타입 유틸리티(Utility Types)를 사용해 기존 타입을 편리하게 조작할 수 있다.
하지만 이 유틸리티들은 대부분 type에서만 자유롭게 사용할 수 있고,
interface에서는 직접 사용이 제한된다.


✅ 타입 유틸리티란?

타입 유틸리티란, 기존 타입을 기반으로 부분 선택, 속성 제거, 변경 등을 편하게 할 수 있도록 도와주는
내장 제네릭 타입 함수이다.


🛠 대표적인 타입 유틸리티 목록

Partial<T>

  • T의 모든 속성을 optional로 만든다.
type User = { id: number; name: string };
type PartialUser = Partial<User>; 
// => { id?: number; name?: string }

Requried<T>

  • T의 모든 속성을 필수값으로 만든다.
type User = { id?: number; name?: string };
type StrictUser = Required<User>; 
// => { id: number; name: string }

Pick<T, K>

  • T에서 특정 속성 K만 골라 새 타입을 만든다.
type User = { id: number; name: string; email: string };
type UserPreview = Pick<User, "id" | "name">;
// => { id: number; name: string }

Omit<T, K>

  • T에서 특정 속성 K를 제외한 새 타입 을 만든다.
type User = { id: number; name: string; email: string };
type WithoutEmail = Omit<User, "email">;
// => { id: number; name: string }

.
.
.

유틸리티설명
Partial<T>T의 모든 속성을 optional로 만듦
Required<T>T의 모든 속성을 필수(required)로 만듦
Readonly<T>T의 모든 속성을 읽기 전용(readonly)으로 만듦
Pick<T, K>T에서 지정한 K 속성만 선택
Omit<T, K>T에서 지정한 K 속성을 제외
Record<K, T>K를 key로 하고 T를 value로 하는 객체 타입 생성
Exclude<T, U>T에서 U를 제외한 타입
Extract<T, U>T에서 U와 겹치는 타입만 추출
NonNullable<T>null 또는 undefined를 제외한 타입 생성
ReturnType<T>함수 타입 T의 반환값 타입 추출
Parameters<T>함수 타입 T의 매개변수 타입들을 튜플로 반환

✅ 해결 방법: type으로 래핑해서 사용
interface를 기반으로 타입 유틸리티를 적용하고 싶다면,
type으로 다시 정의하면 문제 없이 사용 가능하다.

interface User {
  id: number;
  name: string;
}

type PartialUser = Partial<User>; // ✅ 가능!

언제 interface? , type?

정해진건 없지만,

객체의 구조를 정의할 땐 interface

유니언, 튜플, 리터럴 등 복합 타입이 필요할 땐 type

타입 유틸리티(Partial, Omit 등)를 쓰고 싶다면 type

클래스와 함께 사용하거나 선언 병합이 필요하면 interface

등 일정한 기준을 가지고 컨벤션을 정해 코드를 구현하면 바람직할 것 같다.

profile
Frontend Engineers

0개의 댓글