메인 콘텐츠로 건너뛰기
page thumbnail

타입스크립트 기본 문법과 타입 시스템

wislan
wislan
조회수 13
요약

개요

타입스크립트(TypeScript)는 자바스크립트에 정적 타입 시스템을 더한 프로그래밍 언어로, 마이크로소프트가 개발하고 유지·관리하는 오픈 소스 프로젝트이다4. 타입 정보를 코드에 명시할 수 있어 에디터 단계에서 많은 오류를 미리 발견할 수 있고, 대규모 애플리케이션의 안정성과 유지 보수성을 높이는 데 널리 사용된다14.

Generated Image

기본적으로 타입스크립트는 자바스크립트 상위 집합(Superset)이기 때문에 기존 자바스크립트 문법을 그대로 사용할 수 있으며, 여기에 타입 주석과 여러 타입 관련 문법을 추가로 지원한다4. 개발자는 필요한 부분부터 점진적으로 타입을 도입할 수 있기 때문에, 기존 자바스크립트 코드베이스를 서서히 타입스크립트로 전환하기에도 적합하다3.

타입스크립트의 특징과 정적 타입 시스템

타입스크립트의 핵심 특징은 정적 타입 시스템이다. 정적 타입 시스템이란 프로그램을 실행하기 전에, 즉 컴파일 단계에서 변수나 함수의 타입을 검사해 오류를 찾아내는 방식을 말한다1. 이를 통해 잘못된 값 대입, 잘못된 인자 전달, 존재하지 않는 프로퍼티 접근과 같은 오류를 실행 전에 확인할 수 있어 코드의 안정성과 유지 보수성이 향상된다1.

타입스크립트는 자바나 C#처럼 모든 것에 타입을 강제로 붙이게 하는 언어와는 달리, 자바스크립트 개발 경험을 고려해 설계되었다. 필요하면 타입을 명시하고, 그렇지 않은 부분은 자바스크립트처럼 자유롭게 두는 점진적 타이핑(Gradual Typing)을 지원한다3. 점진적 타이핑 덕분에 기존 코드에 한 번에 모든 타입을 붙일 필요 없이, 중요한 모듈부터 서서히 타입을 강화해 갈 수 있다3.

또한 타입스크립트는 타입 추론(Type Inference)을 적극적으로 활용한다. 변수 선언 시 타입을 생략해도, 초기값이나 사용 패턴을 보고 컴파일러가 합리적인 타입을 추론한다3. 이 덕분에 타입을 일일이 적지 않아도 일정 수준의 타입 안전성을 얻을 수 있고, 필요한 부분에만 명시적인 타입을 작성하여 가독성과 안정성을 동시에 추구할 수 있다3.

기본 타입과 타입 주석 문법

타입스크립트의 기본 타입은 자바스크립트의 원시 타입을 바탕으로 한다. 자주 사용하는 것은 number, string, boolean, null, undefined 등이 있으며, 그 외에 any, unknown, void, never 같은 특수 타입도 있다25. 변수에 타입을 지정할 때는 변수 이름 뒤에 콜론(:)타입 이름을 붙이는 타입 주석(Type Annotation) 문법을 사용한다5.

예를 들어, 숫자와 문자열, 불리언 타입을 가진 변수는 다음과 같이 선언할 수 있다.

let age: number = 30;
let username: string = "Alice";
let isAdmin: boolean = false;

타입을 생략하면 타입스크립트가 초기값으로부터 타입을 추론한다. 예를 들어 let count = 0;이라고만 작성해도 count는 자동으로 number 타입으로 추론되고, 이후 문자열을 대입하려 하면 컴파일 오류가 발생한다3. 이런 타입 추론은 간단한 코드에서는 타입 주석을 줄여주고, 복잡한 코드에서는 명시적 타입과 함께 사용되어 개발자가 의도를 명확히 드러내도록 돕는다3.

배열, 튜플, 객체 타입의 표현

배열 타입은 요소의 타입 뒤에 []를 붙이거나, Array<타입> 제네릭 문법으로 표현할 수 있다2. 예를 들어 숫자 배열은 number[] 또는 Array<number>로 정의할 수 있으며, 두 방식은 동등하게 취급된다.

let numbers: number[] = [1, 2, 3];
let names: Array<string> = ["Kim", "Lee"];

튜플(Tuple)은 길이와 각 위치의 타입이 고정된 배열을 표현할 때 사용한다2. 예를 들어 아이디와 이름을 한 쌍으로 다루는 경우, 첫 번째 요소는 숫자, 두 번째 요소는 문자열로 제한하는 튜플을 만들 수 있다.

let userInfo: [number, string] = [1, "Alice"];

객체 타입은 중괄호를 사용해 프로퍼티 이름과 각 프로퍼티의 타입을 지정하는 방식으로 표현한다25. 선택적 프로퍼티는 이름 뒤에 ?를 붙여 선언하며, 이는 있어도 되고 없어도 되는 프로퍼티임을 의미한다.

let user: {
  id: number;
  name: string;
  email?: string;
} = {
  id: 1,
  name: "Alice",
};

타입 별칭과 인터페이스

타입이 복잡해지면 동일한 구조를 여러 곳에서 재사용할 필요가 생긴다. 이때 타입 별칭(Type Alias)을 사용해 특정 타입 구조에 이름을 붙일 수 있다25. 타입 별칭은 type 키워드를 사용해 정의하며, 객체 타입뿐 아니라 유니온 타입, 함수 타입 등에도 적용할 수 있다2.

type User = {
  id: number;
  name: string;
  isAdmin: boolean;
};

let u1: User = { id: 1, name: "Lee", isAdmin: false };

인터페이스(Interface)는 주로 객체의 구조를 정의할 때 사용되는 문법으로, 타입 별칭과 유사하지만 확장(extends)과 선언 병합 등 일부 고유 기능을 제공한다5. 예를 들어 기본 사용자 인터페이스를 정의한 뒤, 관리자 사용자 인터페이스에서 이를 확장할 수 있다.

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

interface AdminUser extends UserBase {
  permissions: string[];
}

타입 별칭과 인터페이스는 많은 경우 서로 대체 가능하며, 프로젝트 스타일과 팀 합의에 따라 선택해 사용하는 경우가 많다. 일반적으로 단순한 유니온 타입 등은 타입 별칭을, 객체 구조나 외부 라이브러리의 형태를 표현할 때는 인터페이스를 사용하는 방식이 널리 쓰인다5.

함수 타입과 매개변수/반환 타입

함수에 타입을 지정할 때는 매개변수와 반환값 모두에 타입 주석을 붙일 수 있다2. 매개변수 이름 뒤에 콜론과 타입을 붙이며, 함수 전체 타입은 (매개변수: 타입) => 반환타입 형태로 표현할 수 있다5.

function add(a: number, b: number): number {
  return a + b;
}

const multiply: (x: number, y: number) => number = (x, y) => x * y;

선택적 매개변수는 이름 뒤에 ?를 붙여 선언하고, 기본값을 가진 매개변수도 자바스크립트와 같이 사용할 수 있다. 이 경우 타입스크립트는 기본값으로부터 매개변수 타입을 추론한다35. 반환 타입은 생략 가능하지만, 복잡한 함수나 라이브러리의 공개 API에서는 명시적으로 적어두는 것이 유지 보수에 도움이 되는 경우가 많다.

함수에 아무 값도 반환하지 않는 경우에는 void 타입을 사용한다2. 반대로 함수가 정상적으로는 끝까지 도달할 수 없는 경우(항상 예외를 던지거나, 무한 루프를 도는 경우)에는 never 타입이 사용되는데, 이는 고급 타입 시스템의 일부로서 제어 흐름 분석과 함께 활용된다5.

유니온, 인터섹션, 열거형과 같은 복합 타입

유니온 타입(Union Type)은 하나의 값이 여러 타입 중 하나일 수 있음을 표현한다2. 파이프(|) 기호를 사용해 여러 타입을 이어 붙이며, 자바스크립트에서 흔히 볼 수 있는 "또는(or)" 관계를 타입 수준에서 명시할 수 있다2.

let id: number | string;
id = 1;
id = "abc";

인터섹션 타입(Intersection Type)은 여러 타입을 모두 만족해야 하는 타입을 정의할 때 사용하며, 앰퍼샌드(&) 기호로 표현한다25. 주로 여러 객체 타입을 합성해서 새로운 타입을 만들 때 유용하다.

type Timestamped = { createdAt: Date };
type Named = { name: string };

type NamedTimestamped = Timestamped & Named;

열거형(Enum)은 여러 상수 값을 하나의 이름 아래 묶어두고, 의미 있는 이름으로 접근할 수 있게 해 주는 문법이다2. 숫자 기반 열거형과 문자열 기반 열거형을 모두 지원하며, 코드의 가독성과 오타 방지에 도움이 된다.

enum UserRole {
  User,
  Admin,
  SuperAdmin,
}

let role: UserRole = UserRole.Admin;

다만 최신 코드베이스에서는 문자열 리터럴 유니온 타입으로 enum을 대체하는 경우도 많다. 예를 들어 type UserRole = "user" | "admin" | "superadmin";처럼 작성하면 별도 enum 없이도 비슷한 효과를 얻을 수 있으며, 트리 셰이킹과 번들 크기 측면에서 장점이 있다는 논의가 있다45.

제네릭을 통한 재사용 가능한 타입 설계

제네릭(Generic)은 함수나 클래스, 인터페이스가 여러 타입을 받아들이면서도 타입 안전성을 유지할 수 있도록 하는 문법이다25. 제네릭을 사용하면 "타입을 매개변수로 받는 함수"를 만들 수 있다. 예를 들어 전달된 값을 그대로 돌려주는 단순한 함수라도 제네릭을 적용하면 다양한 타입에 재사용할 수 있고, 반환 타입이 인자 타입과 정확히 연결되도록 보장할 수 있다.

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

const num = identity<number>(10);
const str = identity("hello"); // 타입 인수 생략 시 추론

배열의 첫 번째 요소를 얻는 함수도 제네릭으로 일반화할 수 있다. 타입스크립트는 제네릭 타입 매개변수에 제약(extends)을 부여해, 특정 속성을 반드시 가지도록 제한하는 것도 지원한다5. 예를 들어 T extends { id: number }와 같이 쓰면, id 프로퍼티가 있는 타입에만 함수 사용을 허용할 수 있다.

제네릭은 컬렉션, 유틸리티 함수, 재사용 가능한 컴포넌트 등을 설계할 때 특히 중요하며, 리액트와 같은 프레임워크의 타입 정의에서도 핵심 역할을 한다14.

타입스크립트 타입 시스템의 설계와 활용

타입스크립트의 타입 시스템은 구조적 타이핑(Structural Typing)을 기반으로 한다5. 이는 "타입 이름"이 아니라 "구조"가 같으면 호환 가능하다고 보는 방식을 말한다. 예를 들어 id: numbername: string을 가진 객체 타입이라면, 그 이름이 다르더라도 구조가 동일하면 서로 대입이 가능하다. 이런 특성은 자바스크립트의 유연한 객체 사용 방식과 잘 맞아 떨어진다5.

또한 타입스크립트는 타입 선언 공간과 값 공간을 구분해 관리한다5. 타입 별칭이나 인터페이스, 클래스의 타입 부분 등은 타입 선언 공간에서만 사용되며, 실제 자바스크립트로 컴파일될 때는 대부분 제거된다. 반면 변수, 함수, 클래스 인스턴스 등은 값 공간에 존재하여 실제 실행 코드로 남는다. 이 구분을 이해하는 것은 타입과 값이 이름을 공유할 때 혼란을 줄이는 데 도움이 된다5.

전반적으로 타입스크립트의 타입 시스템은 자바스크립트 개발자에게 친숙하게 설계되어 있으면서도, 제네릭, 조건부 타입, 매핑된 타입 등 고급 기능을 통해 복잡한 코드베이스를 안전하게 표현할 수 있도록 확장되어 있다45. 이러한 특성 덕분에 타입스크립트는 프론트엔드와 백엔드 모두에서 사실상의 표준 언어로 자리 잡아 가고 있다4.

참고

1리액트의 정석 with 타입스크립트 - https://www.gilbut.co.kr/book/view?bookcode=BN004395

2[Typescript] 1. ts-for-jsdev 타입스크립트 기초 문법 공부 - https://coderdev.tistory.com/80

33.0 타입스크립트 기초 문법 | ts-for-jsdev - https://ahnheejong.gitbook.io/ts-for-jsdev/03-basic-grammar/intro

4한눈에 보는 타입스크립트 | HEROPY.DEV - https://www.heropy.dev/p/WhqSC8

5타입스크립트 타입 시스템 | TypeScript Deep Dive - https://radlohead.gitbook.io/typescript-deep-dive/type-system

Node.js 서버에서 타입스크립트 적용하기

Node.js 서버에 타입스크립트를 적용하려면 먼저 프로젝트를 초기화하고 타입스크립트 관련 패키지를 설치한다.

일반적인 순서는 npm init -ypackage.json을 만든 뒤, 개발 의존성으로 typescript, ts-node, @types/node 등을 설치하는 것이다.

그 다음 npx tsc --init으로 tsconfig.json을 생성하고,

rootDir(예: src), outDir(예: dist), module(예: commonjs), target(예: es2019), esModuleInterop: true 등 Node 환경에 맞게 설정을 조정한다.

이렇게 하면 타입스크립트 컴파일러가 Node 서버 코드를 어떻게 자바스크립트로 변환할지 기준을 갖게 된다.

실제 서버 코드는 src/server.ts 같은 파일로 작성하면서, Express 같은 프레임워크를 쓴다면 express@types/express를 함께 설치해 타입 정보를 활용한다. 예를 들어 간단한 서버는 다음과 같이 작성할 수 있다.

import express, { Request, Response } from "express";

const app = express();
const PORT = 3000;

app.get("/health", (req: Request, res: Response) => {
  res.json({ status: "ok" });
});

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

개발 단계에서는 ts-nodenodemon을 조합해 컴파일 없이 바로 타입스크립트 파일을 실행하고 자동 재시작하도록 설정하는 경우가 많다.

예를 들어 package.json의 스크립트에 "dev": "nodemon --watch src --exec ts-node src/server.ts"를 추가해 개발 서버를 띄우고,

배포 시에는 "build": "tsc"로 타입스크립트 코드를 dist 폴더에 자바스크립트로 컴파일한 뒤 "start": "node dist/server.js"처럼 컴파일된 코드를 실행한다.

이렇게 구성하면 개발 중에는 타입 안전성과 편의성을 모두 누리고, 실제 서버 운영 시에는 순수 자바스크립트로 안정적으로 동작하게 할 수 있다.