TypeScript는 단순히 “자바스크립트에 타입을 붙이는 언어”로 소개되지만, 막상 배우기 시작하면 많은 문법에 당황하게 된다. 타입이라는 개념 자체가 코드에 ‘제약’을 주는 것처럼 느껴지기도 하지만, 실제로는 코드의 명확성, 안정성, 예측 가능성을 높이는 강력한 무기다.
이번 글에서는 TypeScript를 배우면서 반드시 이해하고 넘어가야 할 8개의 핵심 문법을 정리했다. 문법 그 자체보다도 왜 이 문법이 필요했고, 어떤 상황에서 반드시 써야 하는지에 집중했다.
타입 주석(Type Annotation) – 명확한 의도를 코드에 담다
TypeScript의 시작은 변수나 함수에 타입을 명시하는 것이다. 이를 타입 주석이라고 부른다. 예를 들어 어떤 값이 숫자인지, 문자열인지, 불리언인지 명확히 적는 것만으로도 코드의 가독성과 유지보수성이 달라진다.
자바스크립트는 너무 유연해서 때로는 ‘유연성’이 버그를 부른다. 숫자를 기대했는데 문자열이 들어오거나, 함수가 undefined를 반환하는 경우 등등. 타입 주석은 이 애매함을 제거한다. 타입을 명시함으로써 의도를 문서화하고, 개발자 간 오해를 줄이며, 에디터의 자동완성 기능까지 강화된다.
특히 함수의 매개변수와 반환값에 타입을 지정하면, 함수의 계약이 명확해진다. 이는 협업할 때 강력한 신뢰 기반이 된다.
인터페이스(Interface) – 객체의 모양을 계약으로 만든다
객체를 주고받는 일이 많은 자바스크립트에서, 그 객체가 어떤 속성을 가져야 하는지 명확히 하고 싶을 때 사용하는 것이 인터페이스다. 이름, 나이, 주소 등 어떤 속성이 들어가야 할지를 **정의된 형태(Shape)**로 강제하는 문법이다.
인터페이스는 코드 상에서는 보이지 않지만, 타입스크립트가 컴파일 단계에서 구조를 검사해 오류를 잡아준다. API 응답을 객체로 받을 때, 프론트엔드에서 데이터를 상태로 저장할 때, 컴포넌트에 props를 넘길 때 등 다양한 상황에서 객체 구조의 일관성을 유지하는 데 필수적이다.
무엇보다 인터페이스는 다른 타입 간 상속이 가능하고, 선택적 속성도 정의할 수 있어 복잡한 데이터 모델링에도 유연하게 대응할 수 있다.
유니언 타입(Union Type) – 다중 가능성을 품은 타입 선언
자바스크립트는 매개변수가 다양한 타입을 받을 수 있다. 그런데 그 유연성이 오히려 불확실성을 만든다. 예를 들어 매개변수로 number
또는 string
둘 중 하나가 들어올 수 있다면? 이를 표현하는 것이 바로 유니언 타입이다. number | string
처럼 |
연산자를 통해 타입의 가능성을 여러 개 지정할 수 있다.
유니언 타입은 자바스크립트의 자유로움을 유지하면서도, 타입 검사기를 통해 오류를 줄일 수 있다는 점에서 실용성과 안정성을 모두 확보할 수 있는 방식이다.
특히 null
이나 undefined
를 함께 포함시키는 경우 (string | null
), 초기 상태와의 구분이 필요할 때 매우 유용하다. 사용자 입력 처리, 외부 API 응답 처리 등 다양한 실무 케이스에서 활약한다.
타입 가드(Type Guard) – 유연함 속에서 확신을 갖는 방법
유니언 타입이 생기면, 그에 따라 코드 안에서 타입을 ‘확정’하는 작업이 필요하다. 그게 바로 타입 가드다. 유니언 타입을 쓰면 타입스크립트가 ‘이 값이 지금 어떤 타입인지’를 완전히 알지 못하기 때문에, 조건문 등을 통해 타입을 좁히는 작업을 해야 한다.
예를 들어 typeof
, instanceof
, 조건 분기 등을 통해 특정 타입임을 확인하면, 그 블록 내부에서는 해당 타입으로 인식된다. 이렇게 타입을 좁히는 행위를 Type Narrowing, 그 수단이 되는 조건을 Type Guard라고 부른다.
타입 가드는 타입스크립트가 단순한 정적 분석을 넘어 실행 시점의 조건까지 추론할 수 있게 해주는 도구다. 특히 API 응답이 다양한 형태로 올 수 있는 경우, 에러나 예외 상황을 세밀하게 처리하고 싶을 때 필수적인 개념이다.
제네릭(Generic) – 타입에도 유연함을 주는 도구
JavaScript는 함수나 배열 등 다양한 구조에서 타입에 구애받지 않고 동작하는 유연성이 강점이다. TypeScript는 여기에 제네릭을 통해 타입의 유연함을 더했다.
제네릭은 함수나 클래스, 인터페이스 등에 들어올 타입을 외부에서 결정하게 해주는 문법이다. 함수 내부 로직은 바꾸지 않고, 타입만 외부에서 바꿔가며 재사용할 수 있는 구조다.
예를 들어 배열을 복사하는 함수가 있다고 할 때, 그 배열이 number[]
, string[]
, User[]
중 어떤 것이든 동작하길 원한다면, 제네릭이 없으면 각각 타입을 지정한 함수를 따로 만들어야 한다. 하지만 제네릭을 쓰면 하나의 함수로 여러 타입을 커버할 수 있다.
제네릭은 컴포넌트나 훅처럼 재사용성이 중요한 구조에서 가장 많이 쓰이며, 타입 안정성과 유연성이라는 두 마리 토끼를 모두 잡을 수 있게 해준다.
타입 별칭(Type Alias) – 복잡한 타입을 이름으로 간단하게
복잡한 객체 구조나 유니언 타입을 매번 코드에 길게 적기보다는, 간단한 이름을 붙여 관리하고 싶을 때 사용되는 것이 타입 별칭이다. 쉽게 말해 타입에 이름을 붙여주는 기능이다.
예를 들어 유저 데이터가 id: number
, name: string
, email: string
이라면 이 구조를 계속 복붙하는 대신 type User = { ... }
처럼 이름을 붙여 쓰는 것이 훨씬 관리하기 좋다.
타입 별칭은 인터페이스처럼 객체 구조를 표현할 수도 있고, 유니언이나 튜플 타입, 함수 시그니처 등 복잡한 타입도 표현할 수 있다. 특히 API 응답 타입, 내부 상태 모델 등 반복되는 구조를 깔끔하게 관리하는 데 유용하다.
Optional & Nullable – 불확실성을 안전하게 다루는 방법
JavaScript에서는 값이 없을 수도 있다는 상황이 빈번하게 발생한다. 예를 들어 어떤 객체의 email
이 없을 수도 있고, 함수의 반환값이 undefined
일 수도 있다. TypeScript는 이런 경우를 명시적으로 처리할 수 있게 한다.
옵셔널(?
)은 해당 속성이나 매개변수가 선택적임을 명시한다. 즉, 있어도 되고 없어도 된다. 널러블은 값이 null
이나 undefined
일 수 있음을 타입으로 명시하는 것이다.
이 문법들은 단순히 유연함을 주기 위한 것이 아니라, 개발자가 ‘이 값이 없을 수도 있다’는 사실을 인지하고 대응하도록 강제한다는 점에서 매우 중요하다. 이로 인해 예외 상황을 더 명확히 처리할 수 있으며, 런타임 오류를 줄일 수 있다.
타입 추론(Type Inference) – 개발자의 부담을 줄여주는 똑똑한 뇌
TypeScript는 모든 변수나 함수에 타입을 직접 적지 않아도, 코드의 흐름을 통해 타입을 추론할 수 있다. 이를 타입 추론이라고 부른다.
예를 들어 const name = "Alice"
라고 선언하면, TypeScript는 name
이 string
타입임을 자동으로 인식한다. 함수의 반환값도 마찬가지다. return a + b
가 숫자라면, 반환 타입도 number
로 추론된다.
타입 추론은 코드의 간결함을 유지하면서도 타입 안정성을 확보할 수 있게 해준다. 단, 추론이 예상과 다르게 작동하거나 너무 복잡해지면 명시적인 타입 선언이 더 안전한 선택이 될 수 있다.
문법보다 중요한 건 “이 문법이 왜 필요한가”
TypeScript는 처음 접할 땐 복잡하게 느껴지지만, 각각의 문법이 등장한 이유를 이해하고 나면 그 가치가 보인다. 타입을 정하는 이유는 개발자를 불편하게 만들기 위함이 아니라, 더 안정적이고 명확한 코드를 쓰게 하기 위함이다.
타입스크립트의 문법은 결국 더 나은 협업, 더 빠른 디버깅, 더 적은 실수를 위한 도구일 뿐이다. 문법을 억지로 외우는 것이 아니라, 왜 이 문법이 필요한지를 이해하고 맥락에 맞게 쓰는 것이 진짜 실력이다.