[Typescript] 가장 쉬운 타입스크립트 시작하기 ❶

이 글은 [Node.js 백엔드 개발자 되기]에서 발췌했습니다.
골든래빗 출판사

**타입스크립트(Typescript)**는 자바스크립트를 기반으로 정적 타입 문법을 추가한 프로그래밍 언어입니다. 대형 프로젝트에서 많이 사용하며, 요즘 대부분의 회사에서는 팀 작업 시 필수로 선택하는 언어입니다. ES6를 기반으로 한 새로운 문법을 제공하며, 자바스크립트와 완벽히 호환됩니다. 마이크로소프트의 지원을 받아 비교적 최근 언어이지만, 강력한 생태계를 가지고 있습니다.

타입스크립트를 도입하면 자바스크립트 문법을 사용하면서 정적 타이핑을 도입할 수 있습니다. 타입이 미리 정해져 있어야 협업 시에 코드를 읽는 것이 편하고 버그 수정 및 디버깅에 용이하기 때문입니다. 타입스크립트에 익숙하지 않은 분이라면 가장 쉬운 타입스크립트 시작하기로 시작해보세요.

가장 쉬운 타입스크립트 시작하기는 총 2편으로 1편은 ‘타입스크립트 소개와 기초’, 2편은 ‘인터페이스 및 클래스와 고급 기능’을 정리했습니다.

 

[Typescript] 가장 쉬운 타입스크립트 시작하기 ❶

1. 타입스크립트 소개

타입스크립트는 자바스크립트로 컴파일되는* 언어입니다. 자바스크립트는 컴파일되는 프로그램이 아니기 때문에 프로그램 실행 시에 에러가 났을 때야 비로소 잘못된 것을 알 수 있습니다. 또한 갈수록 대규모 서비스와 제품이 증가하고 있습니다. 타입스크립트는 자바스크립트에 타입을 부여했으며 기존 자바스크립트가 안고 있던 결핍을 채워주어 ‘자바스크립트의 미래’로 추앙받고 있습니다.

타입스크립트는 자바스크립트에 타입을 추가한 언어입니다. 타입을 검사하는 정적 타입 검사기이면서, 타입스크립트의 컴파일의 결과가 자바스크립트로 컴파일되는 언어이기도 합니다. 확장자로는 .ts를 사용합니다.

타입스크립트는 마이크로소프트가 개발했으며 2012년에 0.8 버전을 오픈 소스로 공개했습니다. 공개 당시에는 지원하는 IDE가 적어서 언어가 성숙되지 못했다는 비판을 듣기도 했습니다만, 2023년 현재 거의 대부분의 텍스트 에디터에서 지원을 하며, 수많은 자바스크립트 관련 프로그램들이 타입스크립트 기반으로 작성되고 있습니다.

타입스크립트는 타입 선언에 자유를 준 자바스크립트와는 정반대로 강 타입 언어입니다. 언어에는 동적 타입, 약 타입, 강 타입 언어가 있습니다. 동적 타입은 자바스크립트처럼 타입을 지정하지 않고 인터프리터가 타입을 유추하는 언어입니다. 약 타입 언어는 비슷한 타입 간에는 자동으로 변환하는 언어를 의미합니다. 자바가 대표적인 약 타입 언어입니다. 예를 들어 float형 변수에 int 변수를 할당하는 경우 (float a = 1;) 컴파일 에러가 나지 않고, 컴파일러가 내부적으로 float로 변경해서 넣어줍니다. 강 타입 언어는 타입이 같지 않다면 컴파일 에러를 냅니다.

먼저 타입스크립트를 사용했을 때 이점을 알아보고 나서 플레이그라운드에서 실행하기, 노드 런타임에서 실행하기, ts-node에서 실행하기를 순서대로 알아보겠습니다. 본문은 타입스크립트 문법을 익히는 목적이므로 세 방법 중에 원하는 방법 한 가지로 아래 ‘2. 타입스크립트 기초’부터 실습하면 됩니다.

* 엄밀하게 말하면 타입스크립트를 자바스크립트로 변환하므로 트랜스파일한다가 더 정확합니다.

 

1.1 타입스크립트의 이점

자바스크립트에 타입을 추가한 타입스크립트에는 어떤 이점이 있을까요? 최소 두 가지의 이점이 있습니다. 첫 번째는 컴파일 시점에 에러를 확인할 수 있다는 겁니다. 두 번째는 타입이 있기 때문에 개발 툴에서 개발자에게 상대적으로 더 많은 도움을 줄 수 있다는 겁니다. 대표적으로 코드 자동 완성 기능이 있습니다.

입력받은 두 수의 합을 반환하는 자바스크립트 코드가 있다고 해봅시다.

 

// 자바스크립트
function add(a, b) {
  return a + b;
}

 

자바스크립트는 함수 정의 시 매개변수에 타입을 주지 않기 때문에 a, b에 들어가는 값이 숫자인지 문자열인지 알 수가 없습니다. 다음과 같이 매개변수에 숫자를 넣어도 문자를 넣어도 값이 나옵니다. 간단한 함수이지만 사람에 따라 의도와는 다르게 사용할 수 있습니다.

 

add(1, 2) // 3
add('1', '2') // 12

 

같은 함수를 타입스크립트로 만들겠습니다. 함수의 매개변수에서 콜론 왼쪽이 변수명이고, 오른쪽이 타입입니다. number는 숫자 타입을 의미합니다.

 


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


add(3, 5); // 8
add('3', '5') // 컴파일 에러

 

타입스크립트로 만든 함수는 실행 전에 컴파일 에러가 납니다. 코드 작성 시에 IDE에서 함수 정의와 함수 매개변수의 타입도 모두 알고 있기 때문에 코드 자동 완성, 기존에 만들어둔 함수 정의로 이동하기 같은 기능의 지원을 받을 수 있습니다.

 

1.2 플레이그라운드에서 실행하기

타입스크립트 공식 웹사이트에서는 웹브라우저에서 타입스크립트 작성 및 실행을 할 수 있는 플레이그라운드(https://www.typescriptlang.org/play)를 제공합니다. IDE에서 제공하는 기능인 편집 가이드도 동일하게 제공합니다. 플레이그라운드로 가셔서 기본으로 제공되는 hello world 기반으로 사용법과 타입스크립트에서의 타입을 알아보겠습니다.

 

To do

1. 플레이그라운드에 접속합니다. ❶ 샘플 코드가 보입니다. ❷ [Run] 버튼을 클릭해 실행해봅니다. ❸ [logs]탭을 클릭하면 ❹ 출력된 문자열 “hello world”를 확인할 수 있습니다.

 

 

❺ [.D.TS]는선언한변수를보여줍니다(컴파일과정에서생성되는파일입니다). [.D.TS]를클릭해보세요. 그러면 다음과 같은 출력이 보입니다.

 

declare const anExampleVariable = "Hello World";

 

anExampleVariable 변수의 타입이 지정되지 않았음을 쉽게 확인할 수 있습니다. 이렇게 [.D.TS] 기능을 이용하면 쉽게 타입이 지정되지 않은 변수를 찾아 타입을 지정해 혹시 모를 잘 못된 사용을 방지할 수 있습니다.

 

2. 그럼 다음과 같이 변수를 string 타입으로 지정하는 코드를 작성해봅시다.

 

const message: string = "Hello World"; // 문자열 변수에 Hello World값 넣기
console.log(`[${typeof(message)}] ${message.toUpperCase()}`); // 로그 출력

 

message는 string 타입 변수입니다. [.JS]탭을 눌러보세요. 그러면 string이라는 변수 타입이 코드에서 사라진 것을 확인할 수 있습니다.

 

 

이번에는 [.D.TS]탭을 눌러보세요. 변수에 타입을 추가이 추가되어 message 변수의 타입 정의만 보입니다.

 

 

3. 문자열 타입인 message에 숫자를 넣어서 강제로 컴파일 에러를 내봅시다.

 

const message: string = 1;
console.log(`[${typeof(message)}] ${message.toUpperCase()}`);

 

실행하지 않았는데도 [Errors]에 문자열에 숫자를 넣으면 안 된다고 에러가 뜹니다.

 

 

이로써 기본 사용법 안내를 마쳤습니다. 그 외 기능은 한 번씩 눌러보면서 확인해보기 바랍니다.

 

1.3 노드 런타임에서 실행하기

타입스크립트 개발 환경을 구축하려면 Node.js가 먼저 설치되어 있어야 합니다. 여기서는 Node.js를 설치했다고 가정하고 터미널을 켜서 npm으로 타입스크립트를 전역으로 설치해줍시다.

참고로 노드 설치 후에 NestJS를 설치하면 NestJS에서 타입스크립트 컴파일도 환경도 알아서 지원해줍니다. 그런데 지금은 타입스크립트 코드만 단독으로 실행시켜야 하므로 타입스크립트 컴파일 환경을 구축하는 겁니다. 타입스크립트 플레이그라운드를 사용해도 여기서 사용하는 모든 예제 코드의 동작을 확인할 수 있습니다. 플레이그라운드를 사용할 분은 A.2절 ‘타입스크립트 기초’로 건너뛰어주세요.

 

To do

1. 타입스크립트 4.9.5 버전 환경을 명시해 설치합니다. 최신 버전을 설치하고 싶다면 npm install -gtypescript를 실행하면 됩니다.

 

$ npm install -g typescript@4.9.5

 

tsc의 버전을 확인하는 명령으로 tsc가 잘 실행되는지 확인해봅시다.

 

$ tsc --version
Version 4.9.5

 

2. 타입스크립트를 실행할 폴더를 생성하고 tsconfig.json(타입스크립트 설정 파일)을 생성해봅시다.

 

$ mkdir appendix-typescript
$ cd appendix-typescript
$ tsc --init
Created a new tsconfig.json with:
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  skipLibCheck: true
  forceConsistentCasingInFileNames: true
You can learn more at https://aka.ms/tsconfig

 

생성된 tsconfig.json에는 많은 옵션이 있습니다. 그중 target 옵션만 다음과 같이 변경합시다. 기본 타입 중 bigInt는 ES2020 버전 이상에서 제공하기 때문입니다.

 

// ... 생략 ...
"target": "es2020" // 자바스크립트 언어 버전 지정 
// ... 생략 ...

 

tsconfig.json의 옵션은 본문에서 설명하기에는 분량이 부족합니다. 궁금한 독자는 https://aka.ms/tsconfig를 참고해주세요.

 

3. 이전에 만든 타입이 있는 변수 코드를 다음과 같이 수정해봅시다.

 

const message: string = "Hello World";
console.log(message);

 

4. tsc 명령어로 타입스크립트를 자바스크립트로 컴파일할 수 있습니다. 터미널에서 tsc 명령을 실행 해 컴파일을 해봅시다.

 

$ tsc hello-typescript.ts

 

에러가 없다면 같은 폴더에 hello-typescript.js가 생성될 겁니다.

 

▼ appendix-typescript/hello-typescript.js

var message = "Hello World";
console.log(message);

 

5. 이제 Node.js로 실행해봅시다.

$ node hello-typescript.js 
Hello World

 

축하합니다. 타입스크립트를 설치하고, 타입스크립트 환경을 설정하고, 타입스크립트를 자바스크립트로 컴파일한 다음 컴파일된 자바스크립트를 node 런타임 위에서 실행했습니다. 타입스크립트로 만드는 프로젝트들은 모두 이런 식으로 실행이 됩니다.

 

1.4 ts-node를 사용해 실행하기

앞에서 ts 파일을 js 파일로 컴파일해 노드로 실행했습니다. 이번에는 컴파일 과정 없이 바로 실행하는 방법을 알아봅시다. ts-node 패키지를 설치 후 ts-node를 사용해서 실행하면 됩니다.

 

To do

1. ts-node 패키지를 설치합니다.

 

$ npm install -g ts-node

 

2. 설치가 잘되었다면 터미널에서 ts-node를 사용해 ts 파일을 실행해봅니다.

 

$ ts-node hello-typescript.ts 
Hello World

 

지금까지 3가지로 타입스크립트를 실행하는 방법을 알아보았습니다. 앞으로 나오는 코드들은 본인이 원하는 방법을 사용해 실행하면 됩니다.

💡Note: ts-node도 내부적으로는 타입스크립트를 자바스크립트로 변환 후 실행합니다.

 

2. 타입스크립트 기초

타입스크립트의 기초 문법을 빠르게 훑어봅시다. 타입이 있다는 것을 제외하면 자바스크립트와 크게 다른 점은 없습니다.

 

2.1 변수 선언

var, let, const 3가지 키워드로 변수를 선언할 수 있습니다. var는 스코프 규칙*이 다른 언어와는 다릅니다. 다음의 코드를 한번 보시겠습니다.

*변수 접근 범위를 의미합니다.

 

function foo() {
  if (true) {
    var a = 10; 
  }
  return a; 
}

console.log(foo());

// 결과 
// 10

 

이상한 점을 눈치채셨나요? if문 블록 안에서 변수a를 선언하는데 블록 밖에서 리턴을 했고,놀랍게도 원하는 결과가 콘솔에 출력되었습니다. 이처럼 var는 함수 영역 내의 어디서든 접근할 수 있습니다. 그래서 다른 언어를 배운 사람들이 자바스크립트를 처음 사용할 때 실수를 할 여지가 많습니다. 이때문에 최근에는 let과 const를 주로 사용합니다. 방금 작성한 코드에서 var을 let으로 변경하겠습니다.

 

function bar() {
  if (true) {
    let a = 10; 
  }
  return a; 
}

console.log(bar()); // 변수 a가 없는 에러 발생

 

실행 시 다음과 같이 a 변수가 선언되지 않았다고 나옵니다. if문의 블록을 벗어나면 a에 접근을 할 수가 없는 것이죠. let은 블록 (중괄호로 감싼 부분) 스코프로 동작합니다.

같은 스코프를 갖는 const 키워드도 있습니다. 그렇다면 let과 const의 차이점은 무엇일까요? let은 변수의 값을 변경할 수 있습니다. const는 변수의 값을 변경할 수 없습니다. 코드로 살펴봅시다.

 

▼ let으로 선언 시 변숫값 재할당

// let은 변숫값 재할당 가능
let a = 10;
a += 1;
console.log(a); // 11 출력

 

▼ const로 선언 시 변숫값 재할당

// const는 변숫값 재할당 불가
const b = 10;
b += 1; // 에러 출력 
console.log(b);

 

const에 값을 재할당할 수 없다고 해서, 불변*이라고 생각하면 안 됩니다. const로 할당한 변수에서 변경할 수 없는 것은 해당 변수의 메모리 주소 값(레퍼런스)입니다. const에 배열이나 객체 등을 할당하면 값을 변경할 수 있습니다.

* 변수에 값을 할당할 때, 해당 변수는 값의 메모리 주소를 바라보고 있습니다. let은 값의 메모리 주소를 변경할 수. 있지만, const는 변경할 수 없습니다. 변하지 않는 것은 값이 아니라 실제 값을 저장하고 있는 주솟값입니다.

 

2.2 타입 애너테이션

타입 애너테이션(Type Annotations)은 말 그대로 타입을 명시하는 방법을 의미합니다. 자료형에 타입을 표기하는 방법을 변수 타입, 함수에서 매개변수와 결괏값 타입, 객체(Object) 타입 순서로 알아보겠습니다. 먼저 기본적인 자료형 중 하나인 숫자, 문자열, 불리언 타입 변수를 정의하는 방법을 알아보겠습니다. var, let, const 이후에 <변수명> : <타입> 형식으로 변수를 선언합니다.

 

let username: string = "seungkyoo";    // 문자열
let height: number = 179;              // 숫자
let isConditionGood: boolean = true;   // 불리언

 

다음으로 함수에 타입을 선언하는 방법을 알아보겠습니다. 함수에 타입을 지정하는 곳은 기본적으로는 매개변수 부분과 결괏값 부분입니다. 매개변수는 <변수명> : <타입> 형식으로 타입을 지정했습니다. 반환값은 매개변수 뒤에 : <타입> 형식으로 지정합니다.

 

 

마지막으로 객체에서 타입 선언도 살펴봅시다. 객체 타입은 하나 이상의 타입이 섞여 있을 수 있습니다. myInfo라는 변수에 이름(name), 키(height), 컨디션 양호 유무(isConditionGood)를 할당하는 코드를 예시로 살펴보겠습니다.

 

 

객체의 ❶ 타입 선언부와 ❷ 객체에 값을 대입하는 코드입니다. 둘 다 중괄호로 감싼 JSON 형식 입니다. 비교하기 편하도록 표로 확인해봅시다.

 

▼ myInfo 객체가 가지고 있는 변수의 타입 정보

 

만약에 myInfo에 성별도 넣고 싶은데, 필수가 아닌 값으로 정의하는 것이 가능할까요? 네 가능합니다. 변수명 뒤에 ?를 붙여서 선택적 속성(Optional Property)으로 만들 수 있습니다.

 

let myInfoWithGender: {
  name: string;
  height: number; 
  isConditionGood: boolean; 
  gender?: string; // 선택적 속성
}= {
  name: "seungkyoo", 
  height: 179, 
  isConditionGood: true,
};

 

myInfoWithGender 변수의 타입 표기에는 gender가 선택적 속성으로 들어 있기 때문에 값을 할당할 때 넣어주지 않아도 문제가 없습니다. 선택적 속성은 함수의 매개변수를 정의할 때도 가능합니다.

 

// isCritical값은 옵션
function printMessageWithAlert(message: string, isCritical?: boolean): void {
  console.log(message);
  
  if (isCritical) {
    alert(message);
  } 
}

 

2.3 기본 타입 7종

타입스크립트가 제공하는 다양한 타입을 알아보겠습니다. 타입(자료형)은 변수가 어떤 형식의 값을 갖는지 나타냅니다. 타입스크립트는 7가지 기본 자료형을 가지고 있습니다.

  • null:null은의도적으로값이없음을나타낼때사용합니다.
  • undefined : undefined는 변수에 값이 할당되지 않은 경우를 뜻합니다. 즉 의도하지 않은 값의 부재를 의미합니다.
  • boolean: true와 false 두 가지 값을 가지고 있는 타입입니다.
  • string: “타입스크립트” 같은 문자열을 의미합니다.
  • number: 이름처럼 숫자를 의미합니다. 숫자는 123과 같은 정수, 1.23 같은 부동소수점, 16진수, 8진수도 숫자 타입입니다.
  • bigint :매우 큰 숫자도 할당할 수. 있는 타입입니다. 숫자 뒤에 n을 붙여서 123n으로 표시합니다.
  • symbol: 불변이면서 유니크한 값을 표현하는 자료형입니다.

코드로도 살펴보겠습니다.

 

const one: number = 1;                    // 숫자 타입
const myName: string = "seungkyoo";       // 문자열 타입
const trueOrFalse: boolean = true;        // boolean 타입
const unIntended: undefined = undefined;  // undefined 타입
const nullable: null = null;              // null 타입
const bigNumber: bigint = 1234567890123456789012345678901234567890n; // bigint 타입
const symbolValue: symbol = Symbol("symbol");   // symbol 타입

console.log(one + 1); // 2
console.log(myName + " is my name");            // seungkyoo is my name
console.log(trueOrFalse ? "true" : "false");    // true
console.log(bigNumber / 10000000000000000n);    // 123456789012345678901234n

// 모든 값이 유일하므로 같은 값을 넣어도 false
console.log(symbolValue === Symbol("symbol"));

 

타입이 지정되어 있다는 것 이외에는 자바스크립트와 차이가 나지는 않습니다. 여기서는 7가지 기본 타입이 있다 정도만 이해하면 됩니다. 소문자로만 되어 있는 number, string 등의 타입 외에 대문자로 시작하는 Number, String 등의 타입도 있습니다만, 해당 타입은 기본 타입이 아닙니다.

각 타입별로 사용할 수 있는 메서드는 VSCode 등의 편집기를 이용하면 손쉽게 알 수 있습니다. 변수명 다음에 . 기호를 찍으면 사용 가능한 메서드가 다음 그림과 같이 나옵니다. 이를 활용하면 실수가 적어지고, 개발 생산성도 올라갑니다.

 

 

다음 그림은 타입스크립트 타입 계층도입니다. 기본 타입은 굵은 글씨로 표시해두었습니다. 최상 위에 unknown이 있으며 그 아래에 any가 있습니다. 다른 절에서 알아보겠지만, 다른 타입들이 any의 하위에 있기 때문에 타입을 알 수 없는 경우 any를 사용하면 타입 검사를 통과합니다.

 

▼ 타입스크립트 타입 계층도*

 

기본 타입 7종을 알아보았으니 조금 더 복잡한 타입들을 알아봅시다. 기본 타입 다음으로 가장 많이 쓰게 될 타입은 배열과 튜플, 객체 타입입니다. 객체는 이미 ‘2.2 타입 애너테이션’에서 알아보았으니 배열과 튜플을 알아보고 다음으로 any, void. never를 알아봅시다.

 

2.4 배열과 튜플

데이터 여러 개를 넣는다는 면에서 배열과 튜플은 비슷합니다. 크기가 배열은 고정이 아니며, 튜플은 고정이라는 점이 다릅니다. 이에 배열은 각 원소의 타입을 정의하고, 튜플은 원소 개수 만큼 타입을 정의해야 합니다. 배열 코드부터 살펴보겠습니다.

 

const numbers: number[] = [1, 2, 3, 4, 5]; // 1 숫자 배열 const stringArray: Array<string> = ["a", "b", "c", "d", "e"]; // 2 문자열 원소
// 3 스프레드 연산자로 합치기 가능
const oneToTen = [...numbers, ...numbers2]; console.log(...oneToTen);
//4 객체의배열타입
const idols: { name: string; birth: number }[] = [
  { name: "minji", birth: 2004 },
    { name: "hani", birth: 2004 },
   { name: "danielle", birth: 2005 },
   { name: "haerin", birth: 2006 },
   { name: "hyein", birth: 2008 },
];
// 5 배열의 원소가 객체인 타입
const gameConsoleArray: Array<{ name: string; launch: number }> = [
{ name: "플레이스테이션5", launch: 2020 },
{ name: "엑스박스 시리즈 X/S", launch: 2020 }, { name: "닌텐도 스위치", launch: 2017 },
{ name: "스팀덱", launch: 2021 },
];

 

배열은 두 가지 방법으로 정의가 가능합니다. ❶ 첫 번째로는 number[ ]와 같이 자료형 뒤에 대괄호를 써주는 겁니다. number는 숫자이고 [ ]는 배열을 뜻하므로 ‘숫자 배열’로 해석하면 됩니다. ❷ 두 번째는 Array를 쓰고 <> 기호 안에 타입을 적어주는 방식입니다. Array는 배열을 뜻하고 <> 기호는 제네릭(2편 ‘제네릭 함수’ 참조)을 표현할 때 사용합니다. 제네릭은 클래스<타입> 형식으로 씁니다. 즉 Array<string>은 ‘배열 내에 있는 원소들의 타입은 문자열이다.’로 해석하면 되겠습니다. ❸ 배열은 자바스크립트에서와 마찬가지로 스프레드 연산자로 합칠 수 있습니다. ❹ 객체를 배열에 넣어야 하는 경우도 있습니다. 이 경우도 타입[ ] 또는 Array<타입>으로 정의할 수 있습니다. 4 에서는 { name: string, birth: number } 객체 타입의 배열로 선언했습니다. 5 Array<타입>의 경우도 Array<{name: string, launch: number }>으로 Array 뒤 <> 기호에 제네릭 표기법으로 넣었습니다.

다음으로 튜플을 알아보겠습니다. 튜플은 리스트와 비슷하지만 원소 개수가 고정되어 있는 점이 다릅니다. 반환되는 개수가 정해져 있는 곳(예를 들어 여러 개의 값을 동시에 반환하는 함수)에서 사용하면 좋습니다.

 

// ❶ 튜플은원소개수만큼타입정의가필요
const myTuple: [string, number] = ["seungkyoo", 179];

// ❷ 튜플은함수의매개변수가여러개일때유용
function printMyInfo(label: string, info: [string, number]): void {
  console.log(`[${label}]`, ...info);
}

// 결괏값 : [튜플 테스트] seungkyoo 179
printMyInfo("튜플 테스트", myTuple);

// 튜플을 반환하는 함수
function fetchUser(): [string, number] {
  return ["seungkyoo", 179];
}

// ❸ 결괏값을 분해해서 받을 수 있음
const [name24, height24] = fetchUser(); 
console.log(name24, height24);

 

❶ 튜플은 원소 개수가 고정입니다. 그러므로 원소 개수만큼 타입을 지정해줘야 합니다. 대괄호 안에 콤마로 구분해 타입을 지정하면 됩니다. 첫 번째 원소는 string이고 두 번째 원소는 number인 경우 [string, number]로 정의합니다. ❷ 튜플이 유용한 두 가지 경우를 보여줍니다. 첫 번째는 함수에 여러 개의 값을 한 번에 넣고 싶은 경우입니다. printMyInfo는 두 매개변수가 있으며 첫 번째는 string 타입의 label이고 두 번째는 튜플 타입의 info입니다. info 데이터는 여러 가지 데이터를 넣을 수 있도록 튜플로 타입을 선언했고, 출력 시에는 스프레드 연산자로 풀어서 출력합니다. ❸ 튜플이 유용한 두 번째 경우입니다. 함수의 결괏값이 튜플이면 결괏값을 구조 분해(Destructuring)해서 각각 다른 변수에 할당해 받을 수 있다는 겁니다. 데이터베이스에서의 응답이 튜플로 오는 경우가 많아서 잘 사용하면 매우 유용합니다.

 

2.5 any, void, never

다음과 같이 타입을 바꿔가면서 넣어야 하는 코드를 작성해야 한다고 가정합시다.

 

let anyValue = 10;   // ❶ number 타입으로 추론 
anyValue = "hello";  // ❷ 컴파일 에러
anyValue = true;     // ❸ 컴파일 에러

 

타입스크립트는 ❶ 에서 anyValue의 값을 number 타입으로 추론했기 때문에 ❷와 ❸에서는 컴파일 에러가 나게 됩니다. 말도 안 되는 코드일 수 있지만, 이런 코드를 작성해야 한다면 어떻게 할까요? 해결 방법으로는 any와 유니온 타입 두 가지가 있습니다. 유니온 타입은 다음 절에서 다룹니다. any는 unknown을 제외하고 가장 상위에 있는 타입입니다. 그래서 타입을 모르거나 지정할 수 없을 때 사용합니다.

any를 써서 수정한 코드는 다음과 같습니다. 컴파일 에러가 사라지고 실행을 할 수 있게 됩니다.

 

let anyValue: any = 10;
anyValue = "hello";
anyValue = true;

 

💡Note: 타입스크립트로 코드를 작성하고 있으면 다른 서드파티 패키지에서 만든 코드에서 타입이 없는 경우가 가끔 있습니다. 이런 때 any를 사용해 컴파일이 안 되는 상황을 벗어날 수 있습니다. 간혹 타입스크립트에 익숙하지 않아서 any로 타입을 임시 지정하고 넘어가는 경우가 있는데, 이 경우는 추후에 컴파일 시점이 아닌 런타임 시점에 에러가 날 가능성을 높으므로 지양하는 것이 좋습니다.

 

다음으로 void와 never를 알아봅시다. void와 never는 둘 다 함수의 반환값에 지정하는 타입입니다. void는 함수의 결괏값이 없을 때 사용하며 never는 의도적으로 값을 반환하지 않는 때 사용합니다. 예외를 발생시키는 함수이거나 무한 루프를 실행하는 함수가 이에 해당합니다.

 

// 결괏값이 없음
function print(value: any): void {
  console.log(value);
}

// 예외를 던짐
function throwError(message: string): never {
  throw new Error(message);
}

// 무한 루프
function infiniteLoop(): never {
  while (true) {}
}

 

2.6 유니온 타입과 내로잉

유니온 타입을 사용하면 변수 하나를 여러 가지 타입으로 지정할 수 있습니다. 유니온 타입은 값에 허용된 타입을 두 개 이상의 타입으로 확장하는 것을 의미합니다. | 기호로 타입을 구분해 여러 타입을 정의합니다. anyValue는 number, string, boolean 타입을 지정할 수 있어야 하므로 number | string | boolean으로 정의하면 됩니다.

 

let anyValue: number | string | boolean = 10; // ❶ 이 시점에서는 number 
anyValue = "hello";    // ❷ string
anyValue = true;       // ❸ boolean

 

내로잉은 말그대로 타입의 범위를 좁히는 겁니다. 예제 코드에서 anyValue은 값이 할당되는 시점에서 anyValue의 실제 타입이 정해집니다. ❶에서는 number이고 ❷에서는 string이며 ❸ 에서는 boolean이 됩니다.

타입의 범위를 좁히는 데 사용하는 검사 방법을 타입 가드(Type Guard)라고 하며, 값 할당 또는 조건문으로 검사해 타입을 좁힙니다. 조건문에 사용할 수 있는 연산자로 typeof, instanceof, in이 있습니다. 분량 관계상 typeof를 사용한 방법만 살펴보겠습니다.*

 

let anyValue: number | string | boolean = 10;
printAny(anyValue);
anyValue = "hello";
printAny(anyValue);
anyValue = true;
printAny(anyValue);

// 1 매개변수로 number, string, boolean을 할당할 수 있음 
function printAny(value: number | string | boolean): void {
  if (typeof value === "number") {
    console.log(value.toExponential(3));
  } else if (typeof value === "string") {
    console.log(value.toUpperCase());
  } else if (typeof value === "boolean") {
    console.log(value ? "참" : "거짓"); 
  }
}

10
HELLO

 

* 더 궁금한 독자는 공식 문서를 확인해주세요. https://www.typescriptlang.org/docs/handbook/2/narrowing.html

 

❶ printAny( ) 함수는 매개변수로 number | string | boolean의 유니온 타입을 받습니다. 해당 값을 출력 시 타입에 따라서 다르게 출력하고 싶은 경우 typeof로 타입 가드를 사용해 타입을 좁힌 다음 타입별로 다르게 동작하도록 했습니다. 여기서는 다루지 않았지만, instanceof는 클래스의 인스턴스 타입을 좁히거나, in 연산자로 interface로 선언된 객체 내부의 속성으로 구분해 타입을 좁힐 수 있습니다.

 

2.7 타입 별칭

anyValue에 유니온 타입으로 선언할 때 매번 number | string | boolean으로 넣어주는 것이 조금 귀찮지 않으셨나요? 자주 재사용해야 하는 유니온 타입의 경우 타입 별칭을 사용하면 편리합니다. number | string | boolean 유니온 타입을 각각 앞자리 알파벳만 따서 nsb 타입 별칭을 만들어봅시다. 타입 별칭을 만들 때는 type 키워드를 사용해 정의합니다. 코드는 다음과 같습니다.

 

type nsb = number | string | boolean;

 

내가 만든 타입에 추가로 타입을 넣어서 다시 별칭을 만드는 것도 가능합니다. nsb에 null과 undefined를 추가해봅시다.

 

type nullableNsb = nsb | null | undefined;

 

만들어본 별칭들을 사용해서 변수에 타입을 지정하겠습니다.

 

// 타입 별칭
type nsb = number | string | boolean;

let anyValue: nsb = 10;
anyValue = "hello";
anyValue = true;
anyValue = null; // ❶ 컴파일 에러

// 타입 별칭에 null, undefined 추가
type nullableNsb = nsb | null;

let nullableValue: nullableNsb = null;
nullableValue = 20;
nullableValue = "nullable";
nullableValue = false;
nullableValue = undefined; // ❷ 컴파일 에러

 

nsb와 nullableNsb라는 타입을 만들어서 각각 anyValue와 nullableValue에 넣었습니다.

❶ 에서는 컴파일 에러가 나는데 anyValue에는 null을 할당할 수 없기 때문입니다. ❷ 에서는 nullableValue에 undefined를 할당할 수 없으므로 컴파일 에러가 납니다.

 

2.8 인터섹션 타입

유니온 타입은 여러 타입을 지정할 수 있는 타입이었습니다. 예를 들어 number | string으로 지정을 했을 때 둘 중의 한 타입만 만족하면 되었습니다. 반면 인터섹션 타입은 타입 A가 있고 타입 B가 있다면 A이면서 B인 타입을 정의합니다. 그래서 기호도 and를 뜻하는 &를 사용해 A & B로 표현합니다. type 키워드로 타입을 지정 시에 객체의 타입을 지정하듯 { } 기호로 감싸서 이름 속성을 지정할 수 있습니다.

 

type 타입명 ={ 
  속성명: 타입;
}

 

간단한 예제를 하나 만들겠습니다. 컵이라는 타입을 주고 컵에 크기(size)라는 속성을 줍니다. 브랜드라는 타입을 하나 더 만들고 브랜드명(brandName)을 속성으로 줍시다. 그리고 컵과 브랜드를 교차intersection시켜서 브랜드가 있는 컵 타입을 만들어봅시다. 다음은 코드입니다.

 

type cup = {
  size: string;
};

type brand = {
  brandName: string;
};

type brandedCup = cup & brand; // ❶ cup이면서 brand가 있는 타입

let starbucksGrandeSizeCup: brandedCup = {
  brandName: "스타벅스",
  size: "grande",
};

 

❶ 컵 타입과 브랜드 타입을 합쳐서 브랜드가 있는 컵 타입을 만들었습니다. 인터섹션 타입을 복잡하게 만들면, 타입 검사 시 에러가 나는 경우 여러 개의 타입을 동시에 표시해야 하므로 에러 메시지를 이해하기가 힘들어집니다. 간결함을 유지해 사용하는 것이 좋습니다.

 

💡Warning: 인터섹션 타입을 잘못 사용하면 값을 할당할 수 없는 타입을 만들게 될 수 있습니다. 기본 타입인 number와 string을 합쳐서 숫자이면서 동시에 문자열인 타입을 만드는 경우입니다.

 

type impossible = number & string;
let testImpossible: impossible = 10; // Error

 

이 경우는 타입을 지정하면 무조건 컴파일 에러가 나게 됩니다. 사용에 주의해야 합니다.

 

2.9 리터럴 타입

기본 타입 중 참, 거짓을 나타내는 boolean은 true와 false값만 가지고 있습니다. 기본 타입의 값들을 조합해서 한정적인 값들만 나타내는 타입을 만들 수 있는데 이를 리터럴 타입(Literal type)이라고 합니다. 예를 들어 커피 크기가 small, medium, large만 있다고 가정해봅시다. 언급한 3가지 문자열만가질수있는타입을만들수있습니다.

|로세값을연결하면리터럴타입을만들수있습니다.

 

type CoffeeSize = "small" | "medium" | "large";

let myCoffeeSize: CoffeeSize = "small";
let starbucksCoffeeSize: CoffeeSize = "tall"; // ❶ 타입 에러

 

❶ 에서 “tall”은 할당되지 못하고 에러가 나는데, 이는 CoffeeSize 타입에는 “small”, “medium”, “large”만 할당할 수 있기 때문입니다.

 

2.10 함수 타입

자바스크립트와 타입스크립트에서는 함수를 값처럼 사용할 수 있습니다. 이 말은 어떤 변수에 할당된 값이 함수라면 그 타입은 함수 타입이라는 겁니다. message라는 문자열 하나를 받아서 console.log로 출력하고 같은 값을 결괏값으로 반환하는 echo( ) 함수를 만들겠습니다.

 

function echo(message: string): string {
  console.log(message);
  return message;
}

 

코드는 설명이 필요 없을 정도로 단순합니다. 자 그러면 함수를 값으로 다룰 수 있다고 했으니 값에 할당해봅시다.

 

const funcEcho = echo;

 

funcEcho의 타입은 어떻게 될까요? 우선 아무런 타입 지정 없이 함수를 값으로 할당해도 타입 에러가 나지 않습니다. 타입 에러는 나지 않지만, 타입을 모르겠을 때 변수 위에 마우스 커서를 올려봅시다. VSCode는 다음 그림과 같이 타입을 추론해 보여줍니다.

 

 

funcEcho의 타입은 (message: string) => string입니다. 괄호 안은 매개변수 정의 부분이며 ⇒ 기호 다음의 string은 반환 타입을 의미합니다. 매개변수가 문자열 변수 message 하나이며 문자열을 반환 타입으로 가진다는 의미입니다. funcEcho의 타입을 알았으니 코드도 수정해줍시다.

함수 타입을 타입 별칭으로 지정할 수도 있습니다.

 

type FuncEcho = (message: string) => string;
const funcEcho2: FuncEcho = echo;

 

기존과 동일하게 type 타임명 = 타입 형태로 작성해주면 됩니다. 코드에서는 FuncEcho 타입으로 타입을 만들고 funcEcho2 변수에 echo( ) 함수를 할당했습니다.

type 지정 시 객체 타입의 속성으로 함수를 지정할 수 있을까요? 가능합니다.


type FuncEcho3 = {
  (message: string): string; // ❶ =>가 없는 것에 주의
};
const funcEcho3: FuncEcho3 = echo;
funcEcho3("test3");          // ❷ 함수의 타입을 자동으로 추론해 실행
funcEcho3(123);              // 매개변수가 문자열이 아니므로 타입 에러

 

❶에 함수 정의가 있습니다. 타입 별칭에 선언하는 것과 달리 객체의 속성에서는 => 대신 :기호를 사용합니다. ❷ funcEcho3은 FuncEcho3 타입입니다. FuncEcho3 타입에 있는 함수 타입은 이름이 따로 선언되어 있지 않아서, 이름으로 할당할 수 없습니다. 타입스크립트는 FuncEcho3 타입의 정의를 보고 맞는 함수를 찾아서 실행시켜 주게 됩니다. 만약 여기서 funcEcho3(123)을 하면 매개변수 타입이 달라서 타입 에러가 나게 됩니다.

 

여기까지 타입스크립트를 활용하기 위한 기초를 알아보았습니다.

2편에서는 한 걸음 더 나아간 기능을 공부하겠습니다.

박승규


아직도 개발이 재미 있는 15년차 천상 개발자입니다. 웹 개발, 게임 백엔드 개발, 플랫폼 및 인프라 개발 등 다양한 영역을 경험했습니다. 현재는 카카오엔터테인먼트에서 백엔드 개발자로 일합니다.


현) 카카오엔터테인먼트 페이지 서비스 개발팀
전) 트리노드 (포코팡, 포코포코) 서버 개발자
전) NHN Japan 플랫폼 개발팀

Leave a Reply

©2020 GoldenRabbit. All rights reserved.
상호명 : 골든래빗 주식회사
(04051) 서울특별시 마포구 양화로 186, 5층 512호, 514호 (동교동, LC타워)
TEL : 0505-398-0505 / FAX : 0505-537-0505
대표이사 : 최현우
사업자등록번호 : 475-87-01581
통신판매업신고 : 2023-서울마포-2391호
master@goldenrabbit.co.kr
개인정보처리방침
배송/반품/환불/교환 안내