기본 문법과 작동 방식
Chapter 1 - 타입스크립트의 기본 문법
시작하며
업무를 위해 Next.js를 사용하게 되면서 자연스럽게 리액트와 타입스크립트를 다루어야 할 기회가 생겼다. 약 9개월간 익힌 정보를 최대한 정리하면서 다듬어보는 것을 목표로 삼고 작성하기로 하였다.
타입스크립트
타입스크립트 이전에도 'CoffeeScript'같은 시도들이 있었지만 MS라는 거대한 스폰서와 넓은 생태계는 자연스럽게 타입스크립트를 폭발적인 성장으로 이끌었다. 또 하나 크게 영향을 준 것은 문법에 있다고 생각한다. 전임자들은 자바스크립트의 복잡함을 줄이려는 시도에 가까웠기 때문에 'perl'이나 'python'을 많이 닮아 있었다.
문법적 간결함으로 본다면 비교할 필요가 없을 만큼 축약되고 자연어에 가깝게 표현되어 목적을 충분히 달성했다고 할 수 있다. 하지만 '비교적' 가볍게 발을 들이는 자바스크립트 사용자들에겐 상당히 생소한 문법들이 많았고 추가적으로 배워야 할 문법이 많아지는 부작용도 가지고 있었다. 조금만 긴 코드가 된다면 이미 자바스크립트가 아닌 형태가 되어버리고 많은 라이브러리들이 Coffee Script 형식의 예제를 제시하는 것도 아니었다. 반대로 한 번 적용되어버린 프로젝트의 경우 다시 되돌리기가 힘든 점도 있다. 이런 불편함들이 쌓여 결국 자연스럽게 사용자들이 기피하게 되는 주요 원인이 되었다고 생각한다.
타입스크립트는 JavaScript 본연의 문법에 추가적인 메타데이터를 붙여 넣는 식으로 문법을 구성하였다. 사실 두 문법은 비교 군으로 잡았을 뿐이지 목적이 다른 도구이다. 타입스크립트는 '자바스크립트에서 명시적으로 형을 지정할 수 있게 해 오류를 줄인다'가 가장 큰 목적이다. 그 때문인지 타입스크립트는 C# 또는 Java와 같이 비교적 최신 OOP 프로그래밍 언어가 가지는 문법적 특색을 지원하였는데 이는 기존 사용자도 큰 어려움 없이 적응할 수 있었고 오늘날 자바스크립트 판때기의 최강자로 군림할 수 있게 했다.
프론트엔드가 지옥이었던 이유
- 2. 기본 자료형
- 3. 자료형과 참조형
- 14. 클래스와 팩토리 디자인 패턴
- 16. new, 생성자, instanceof 그리고 인스턴스
- 17. 프로토타입 상속과 프로토타입 체인
- 21. 클로저
- 25. 프로미스
- 30. 상속, 다형성 그리고 코드 재사용
타입스크립트의 발전과 자바스크립트 내장 기능이 개선되면서 요즘엔 스크립트 코드가 많이 직관적으로 변모하였다. 그 이전을 이야기하자면 쉽게 '난장판'이라고 표현할 수 있다. class
, import
, extends
, private
, const
, let
, fetch
등등... 지금은 너무도 당연하게 사용하고 있지만 이 내장기능 없이 프론트엔드를 구성한다고 상상해 보자. 꽤나 고된 작업이었다.
클로저와 호이스팅의 장난질을 뚫고 직접 프로토체인을 만들어 연결해나가며 requirejs를 통해 모듈의 동적인 로드를 직접 구현하고서 결국엔 IE라는 좌절을 삼키며 수많은 키보드를 눈물로 얼룩지게 하는... 객체 지향 언어임에도 객체로 유기적이고 효율적인 프로그램을 만든다는 것이 어려웠다. 어쩌면 애초에 할 수 없는걸 붙잡고 있었는지도 모르겠다.
타입 스크립트는 이런 악독한 가독성의 코드를 개선하기 위해서 그리고 실행자까지 알 수 없는 변수를 예측하기 위해 문법적 보조 역할로 사용한다. 이름에서도 알 수 있듯이 자바스크립트의 널널한 문법적 허용범위를 아주 정확하고 명시적으로 바꿔준다. 어디까지나 보조도구로써 타입스크립트를 바로 실행할 수는 없고 작성된 타입 스크립트 코드를 인터프리터가 실행할 수 있는 일반 자바스크립트 코드로 변환하는 컴파일 단계를 거쳐야 한다.
let str: string = "";
let num: number = 10;
num = str; // 숫자형 변수에 문자열을 넣으려 하였으므로 오류 발생.
이 컴파일 과정에서 타입 스크립트는 자료형을 확인하고 오류가 발생할 수 있는 코드를 실행전에 찾아낼 수 있다. 오류 없이 컴파일이 완료되면 타입 스크립트로 작성된 코드들이 모두 제거되고 실행 가능한 자바스크립트만 남게 된다. 그래서 강제로 컴파일 오류를 무시하고 자바스크립트를 만들어 실행하면 우리가 알고 있는 자바스크립트로 실행된다. 타입스크립트는 어노테이션 혹은 더 엄밀히 말해 메타데이터에 가까운 도구인 것이다. 예외적으로 enum
처럼 런타임에서도 사용할 수 있는 코드들이 있긴 하지만 전체적인 콘셉트에 대한 설명이니 나중에 소개하도록 하겠다.
문법적 기초
자료형 지정
// 변수
const str: string = "";
const bool: boolean = false;
const type: "typeA"|"typeB"|"typeC"|"typeD" = "";
// 함수
const fn: (param: number) => string = (param) => String(param);
// 객체 리터럴
const object: { a: string; b: number } = {
a: "str",
b: 10
};
객체
interface Quadruped {
name: string;
run(steps: number): void;
}
class Dog implements Quadruped {
name: string;
constructor(name: string) {
this.name = name;
}
run(steps: number): void {
console.log(`${this.name} ran ${steps} steps`);
}
}
class Cat implements Quadruped {
name: string;
constructor(name: string) {
this.name = name;
}
run(steps: number): void {
console.log(`${this.name} ran ${steps} steps`);
}
}
const autumny: Quadruped = new Dog("Autumny");
const snowy: Dog = new Dog("Snowy");
const spriny: Cat = new Cat("Spriny");
타입스크립트에는 interface
문법을 지원한다. 경험해본 사람은 문법적인 규칙만 익숙해지면 유용하게 사용할 수 있다.
일반 객체
const data: { body: { name: string } } = { body: { name: "hello" } };
사용자 정의와 확장
type DataRecord = { body: { name: string } };
type ResponseRecord = DataRecord & { status: number }
type ResponseData = (data: ResponseRecord) => void;
const data: DataRecord = { body: { name: "hello" } };
const res: ResponseRecord = { body: { name: "hello" }, status: 204 };
const responseData: ResponseData = (data: ResponseRecord) => {
// codes ...
}
타입이 복잡해진다면 새로운 타입(자료형)을 선언하여 사용할 수 있다. 타입은 컴파일 이후 코드에서 모두 제거되며 당연하게도 런타임에서는 사용할 수 없다. 오직 타입스크립트의 문법을 사용할 수 있는 곳에서만 사용할 수 있다.
enum
// 번호 자동 매김
enum STATUS_MESSAGE {
"NOT_FOUND",
"NO_CONTENT",
"INTERNAL_SERVER_ERROR",
"OK",
}
// 번호 사용자 정의
enum STATUS_CODE {
"NOT_FOUND" = 404,
"NO_CONTENT" = 204,
"INTERNAL_SERVER_ERROR" = 500,
"OK" = 200,
"EXCEPTION" = "This Is Exception",
}
열거형은 런타임에서 동작하는 코드로 상수를 선언할 때 유용하다. 각각의 키들은 코드를 작성할 때 사람이 인지할 수 있는 변수로 접근할 수 있으며 컴파일 이후에는 자동으로 숫자가 매겨져 효율적으로 사용할 수 있다. 사용자 정의에서는 번호 대신 문자열을 입력할 수도 있다.
// 값과 키 모두 문자열로 지정
enum STATUS_MESSAGE {
"NOT_FOUND" = "NOT_FOUND",
"NO_CONTENT" = "NO_CONTENT",
"INTERNAL_SERVER_ERROR" = "INTERNAL_SERVER_ERROR",
"OK" = "OK",
}
// keyof typeof를 활용하여 키목록을 타입으로 선언
type MessageFunction = (status: keyof typeof STATUS_MESSAGE) => void;
const sendMessage: MessageFunction = (status) => res.send(status);
// 사용 예시
sendMessage(STATUS_MESSAGE.OK); // OK
sendMessage(STATUS_MESSAGE.NO_CONTENT); // NO_CONTENT
VS Code 코드 어시스트
03. 유틸리티에서 작성할 keyof
, typeof
문법을 활용하면 좀 더 직관적이고 개연성있는 코드 어시스트를 제공할 수 있다. 키와 값을 동일한 문자열로 구성하여 데이터베이스에 저장되는 값들도 일관되게 저장할 수 있다. enum
을 숫자로 지정하는 것이 익숙한 사람이라면 keyof typeof
를 제거하고 사용하면 된다. 디비에 숫자로 저장되기 때문에 코드표를 추가로 제공해야 한다.
인터페이스와 타입의 차이
type DataObject = { status: number };
type DataExtendObject = DataObject & { name: string };
interface DataInterface {
status: number;
}
interface DataExtendInterface extends DataInterface {
name: string;
}
const data1: DataExtendObject = { status: 200, name: "dataExtendObject" };
const data2: DataExtendInterface = { status: 204, name: "dataExtendInterface" };
위 코드는 정상 작동한다. 타입스크립트를 사용하다 보면 type
과 interface
에 대해서 의문을 가지게 된다. 이 두 가지는 문법적 차이 외에는 차이점이 없다는 것을 깨닫게 될 것이다. 타입의 경우 const
와 마찬가지로 한 번의 선언만이 가능한데 반해 인터페이스의 경우에는 선언된 모든 인터페이스들이 병합되어 작동한다. 프로젝트 전체에 확장이 필요한 자료형은 interface
, 상수적인 자료형은 type
을 사용하여 선언하면 된다.
이러한 작동 방식은 관리 측면에서 인터페이스를 사용할 경우 가독성을 크게 떨어뜨릴 수 있을 것처럼 보이지만 상식적으로 인터페이스명을 동일하게 구성하여 여러 곳에 코드를 분산시켜 관리한다는 것은 일반적인 경우가 아닐 것이다.
타입스크립트측에서는 "인터페이스가 유연성과 성능 등 더 이점이 있다" 설명하며 인터페이스를 기본으로 사용하되 타입이 필요할 때에만 쓰도록 권장한다. 반드시 클래스를 위한 선언뿐만 아니라 객체의 유형을 정하는 전반에 사용하는 것을 권장하고 있다.
초판: 2025. 10. 02. 13:26:01
© 2025 이 문서는 "CC BY 4.0 국제규약" 라이선스로 배포 되었습니다. 모든 권리는 저자에게 있습니다.