logo

기본 문법과 작동 방식

Chapter 1

56 조회

0 추천

2,187 단어

11분 예상

08/28/2024 게시

luasenvy 작성

CC BY-NC-SA 4.0

시작하며

업무를 위해 Next.js를 사용하게 되면서 자연스럽게 react, 그리고 타입스크립트를 다루어야할 기회가 생겼다. 약 9개월간 익힌 정보를 최대한 정리하면서 다듬어보는 것을 목표로 삼고 작성하기로 하였다.

타입스크립트

타입스크립트를 사용하는 이유는 아주 다양했다. 타입스크립트 이전에도 CoffeeScript같은 비슷한 시도들이 있었지만 MS라는 큰 스폰서의 힘과 거대한 커뮤니티를 가지게 된 타입스크립트는 폭발적인 성장으로 오늘날 자바스크립트 판떼기에서 최강자로 군림하게 되었다.

목록: 자바스크립트가 지옥이었던 이유의 작은 조각들...

타입스크립트의 발전과 자바스크립트 내장 기능이 점점 개선되면서 직관적인 코드로 변모하고 있지만 class, import, extends, private 등등 내장기능이 없던 시절을 돌이켜 본다면 꽤나 고된 작업이었다.

클로저와 호이스팅의 장난질을 뚫고 직접 프로토체인을 만들어 연결해나가며 requirejs를 통해 모듈의 동적인 로드를 직접 구현하고서 결국엔 IE라는 좌절을 삼키며 수 많은 키보드를 눈물로 얼룩지게 하는... 객체 지향 언어임에도 객체로 유기적이고 효율적인 프로그램을 만든다는 것이 어려웠다. 어쩌면 애초에 할 수 없는 걸 붙잡고 있었는지도 모르겠다.

변수의 값을 실행전까지는 뭐가 할당될지 예측이 안된다거나 악독한 가독성의 코드를 개선하기 위하여 문법적 보조 역할로 타입스크립트를 사용한다. 이름에서도 알 수 있듯이 자바스크립트의 널널한 문법적 허용범위를 아주 정확하고 명시적으로 바꿔준다. 어디까지나 보조도구로써 타입스크립트를 바로 실행할 수는 없다. 타입스크립트로 작성된 코드는 한 번 컴파일을 거쳐 인터프리터가 실행할 수 있는 일반 자바스크립트 코드로 변환되어야 한다.

let str: string = "";
let num: number = 10;

num = str; // 숫자형 변수에 문자열을 넣으려 하였으므로 오류 발생. 

이 변환 과정에서 타입스크립트로 작성된 코드는 자료형을 확인하여 오류가 발생할 수 있는 코드를 사전에 찾아낼 수 있다. 오류없이 컴파일이 완료되면 타입스크립트로 작성된 코드들이 모두 제거되고1 실행가능한 자바스크립트만 남게 된다.

그렇기에 컴파일 오류를 무시하고 자바스크립트를 만들어 실행한다면 타입스크립트 오류와는 전혀 상관없이 우리가 알고 있는 자바스크립트로 실행된다.2

문법적 기초

자료형 지정

// 변수
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

코드 어시스트

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" };

위 코드는 정상 작동한다. 타입스크립트를 사용하다보면 typeinterface의 문법적 차이 외에는 없다는 것을 알게 된다. 문제가 발생하면 컴파일러가 알려주기 때문에 자유롭게 선택해서 사용해도 된다고 하지만 실제로는 선언에서 차이점을 보인다. 타입의 경우 const와 마찬가지로 한 번의 선언만이 가능한데 반해 인터페이스의 경우에는 선언된 모든 인터페이스들이 병합된다.

이러한 작동방식은 관리측면에서 인터페이스를 사용하는 것이 가독성을 크게 떨어뜨릴 수 있을 것 처럼 보이지만 상식적으로 인터페이스명을 동일하게 구성하여 굳이 여러곳에 코드를 분산시켜 관리한다는 것은 일반적인 경우가 아닐것이다.

타입스크립트에서는 인터페이스가 유연성과 성능 등 더 이점이 있다고 설명하며 인터페이스를 기본으로 사용하되 타입이 필요할 때에만 쓰도록 권장한다. 반드시 클래스를 위한 선언뿐만 아니라 객체의 유형을 정하는 전반에 사용하는 것을 권장하고 있다.

Footnotes

  1. 예외적으로 enum과 같이 런타임에서도 사용할 수 있는 코드들이 있긴 하지만 전체적인 컨셉에 대한 설명이니 나중에 소개하도록 하겠다.

  2. 어노테이션 혹은 더 엄밀히 말해 메타데이터에 가까운 도구인 것이다.

실전압축 타입스크립트 시리즈