본문 바로가기

Typescript/Basic

TypeScript - tutorial

 

* 목차

 1. Intro

 2. TypeScript Compiler 설치

 3. 간단 Typescript 예제

   3.1. number

   3.2. string

   3.3. boolean

   3.4. object

     3.4.1 interface

     3.4.2 type

   3.5. array

   3.6. tuple

   3.7. enum

   3.8. any

   3.9. void

   3.10. never

   3.11. String Literal Types

   3.12. function

   3.13. generic

      

   

 

 

* 본 글은 javascript에서 정형화 되어 있지 않는 type으로 인해 고통받으신 분들을 위한 글입니다. javascript의 기본 지식을 필요로 합니다.

 


Intro

JavaScript는 Type과 상관없이 연산을 성공하는 경우가 있는데 이는 개발자가 의도하지 않는 동작을 야기합니다.

 

예를 들어 숫자 1과 글자 1을 더한다고 합시다. 보통의 개발자는 이러한 연산 자체를 Error로 생각합니다. 그러나 JavaScript는 Error를 띄우지 않고 그대로 연산을 성공합니다.

console.log(1+"1")
11 // 누가 이런 동작을 의도하고 개발할까요?

 

참고로 Generic programming(Type을 runtime에 정할 수 있는)을 지원하는 python이라도 저런 동작은 error를 일으킵니다.

Python 3.11.1 (tags/v3.11.1:a7a450f, Dec  6 2022, 19:58:39) [MSC v.1934 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> 1+"1"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

 

이렇게 Error가 발생해야 할 것 같은 연산에서 Error가 발생하지 않는 몇가지 Case때문에 JavaScript의 Error 예외처리를 하기 힘들어지고 이는 Application에 결점, 취약점을 쉽게 제거할 수 없다는 뜻입니다. 이러한 점을 보완하기 위해 JavaScript에서 Type을 적용해 미리 Type 검사를 할 수 있도록 하는 것입니다. 그렇게 한다면 1+"1"과 같은 연산에 대해서 사전에 점검하고 Error를 띄울 수 있겠죠? 이렇게 Type을 명시한 언어를 TypeScript라고 합니다.

 

TypeScript는 안타깝게도 Browser가 바로 이해할 수 있는 언어가 아닙니다. 그래서 TypeScript로 application을 작성한 후 tsc 명령어로 type을 검사 후 JavaScript로 변환해 Browser가 이해할 수 있는 언어로 바꿔줍니다. 이 점을 이해하셔야 TypeScript의 특징을 이해하실 수 있습니다. JavaScript에 새로운 함수같은 것이 더해지는 것이 아닌, Type만을 검사하는 언어라는 것을 확실히 했으면 합니다.

 


TypeScript Compiler 설치

설치는 다음과 같이 진행합니다. 별 문제가 없으면 global하게 설치합시다.

npm install -g typescript

 

만약 yarn을 사용중이시면 이렇게 설치합시다.

yarn global add typescript

 

저는 yarn을 사용해서 설치를 진행했습니다.

 

설치가 완료되었으면 tsc를 입력해 보도록 합시다. 아래와 같이 command 목록이 뜬다면 설치가 성공적으로 된 것입니다. 이제 예제를 실행해 보도록 합시다.

TypeScript Compiler의 다양한 Command를 볼 수 있다.

 

* 만약 tsc를 입력했는데 에러가 발생한다면 아래의 글을 참조해 봅시다.

 

[Error] yarn global add typescript - Command 'tsc' not found, but can be installed with:sudo apt install node-typescript

* 바쁘신 분들을 위한 빠른 설정 yarn config set prefix ~/.yarn-global yarn global add typescript echo 'export PATH="~/.yarn-global/bin:$PATH"' | tee -a ~/.bashrc source ~/.bashrc tsc * 환경은 ubuntu 기준으로 설명합니다. [실습] EC2

tyoon9781.tistory.com

 


이제 tsc 명령어를 통해서 TypeScript로 짠 파일을 JavaScript 언어로 변환(Compile)할 수 있습니다. 연습파일을 하나 만들어 보겠습니다.

 

index.ts

let count: number = 1;
console.log(count);

 

이렇게 한 다음에 terminal에서 tsc 명령어를 활용해 index.ts를 index.js로 변환하겠습니다.

tsc index.ts

 

이렇게 하면 index.ts를 기반으로 만들어진 index.js를 확인해 볼 수 있습니다. 이 index.js는 index.ts를 기반으로 생성되었으며 browser가 읽을 수 있는 파일이 되었습니다.

 

index.js

var count = 1;
console.log(count);

 

만약에 index.ts에 type을 잘못 지정하면 어떻게 될까요?

 

index.ts (틀린 예)

let wrongCount: string = 1;
console.log(wrongCount);

 

이 상태에서 tsc를 동작하면? 어디서 에러가 생겼는지 잡아줍니다.

 

하지만 javascript 파일은 여전히 생성된 것을 확인할 수 있습니다. 이렇게 Type Error를 감지했는데도 javascript가 그대로 생성되는게 싫으신 분들은 noEmit option을 추가하면 됩니다.

tsc --noEmit index.ts

 

자 이제 TypeScript를 어떻게 구동해야 하는지 원리를 알았으니 이제 본격적으로 TypeScript 문법을 알아보겠습니다.

 


Basic Type

변수의 Type을 명시하는 방법을 알려드리겠습니다. 변수 뒤에 :과 type을 적는 것으로 TypeScript는 변수의 Type을 인식합니다. 

let 변수이름: 타입 = 값

 

Basic Type으로는 간단하게 아래와 같은 Type이 있습니다. 하나씩 알아봅시다.

 

1. number

숫자. int와 float를 구분하지는 않는다.

let age: number = 30;

 

2. String

문자열.

let firstName: string = "john";

 

3. Boolean

True, False

let isMember: boolean = true;

 

4. Object

key, value 구조로 되어 있는 데이터 형식. 여기서 만약에 object의 내부 type도 지정하고 싶다면 interface나 type을 사용하시면 됩니다.

let person: object = {
    firstName: 'john',
    age: 30,
    isMember: true,
}

 

4.1 interface

object의 구조를 정의하는데 사용. 상속을 지원하며 다른 interface로부터 확장할 수 있다.

interface Person {
    firstName: string;
    lastName?: string;
    age: number;
    isMember: boolean;
}

 

위의 구문에서 ?가 사용되었는데 이는 lastName이 없어도 괜찮다는 의미입니다. 하지만 나머지 (firstName, age, isMember)는 빠지지 않고 object에 포함되어 있어야 합니다. 만약 밑과 같이 작성하게 된다면 다음과 같은 에러를 확인할 수 있습니다.

lastName은 확인하지 않고 isMember가 없다는 error를 보여주고 있다.

 

4.2 type

type 예약어는 객체 뿐 아니라 모든 Type에 사용 가능. Union, Intersection, Tuple 등 더 다양한 연산을 지원한다.

type Person = {
    firstName: string;
    lastName?: string;
    age: number;
    isMember: boolean;
}

 

아까 interface와 어떤점이 다른걸까요? 우선 "=" 연산자 문법이 다릅니다. 그리고 type은 object뿐만 아니라 더 많은 type에서 사용할 수 있고, 연산자도 지원합니다. 아래는 type을 합쳐서 정의하는 문법입니다.

type Person = {
    firstName: string;
    lastName?: string;
    age: number;
    isMember: boolean;
}

type Teacher = Person & {
    subject: string;
    teachingGrade: number;
    classNumber: number;
}

let teacher: Teacher = {
    firstName: "sue",
    age: 30,
    isMember: false,
    subject: "english",
    teachingGrade: 3,
    classNumber:5,
}

 

interface는 이런 연산을 상속(extends)를 통해서 같은 기능을 지원합니다.

interface Person {
    firstName: string;
    lastName?: string;
    age: number;
    isMember: boolean;
}

interface Teacher extends Person {
    subject: string;
    teachingGrade: number;
    classNumber: number;
}

let teacher: Teacher = {
    firstName: "sue",
    age: 30,
    isMember: false,
    subject: "english",
    teachingGrade: 3,
    classNumber:5,
}

 

여기까지 보면 interface와 type은 그렇게 큰 차이는 없어 보입니다. 실제로도 많은 부분은 서로 호환이 가능합니다. 하지만 저는 개인적으로 type을 더 선호합니다. 직관적인 연산 지원이 큰 이점으로 다가오기 때문입니다.

 

5. Array

같은 Type이 여러개 나열되어 있는 형식

type Person = {
    firstName: string;
    lastName?: string;
    age: number;
    isMember: boolean;
}

let people: Person[] = [
    { firstName: "sue", age: 30, isMember: false },
    { firstName: "john", age: 25, isMember: true },
    { firstName: "david", age: 21, isMember: false },
    { firstName: "keth", age: 30, isMember: true },
    { firstName: "jojo", age: 30, isMember: true },
];

 

Array의 특징은, []안에 있는 data의 type이 모두 동일하다는 것입니다. 만약 2개 이상의 type을 다루고 싶다면 or 연산자를 활용해서 다양한 type을 정의할 수 있습니다.

let data: number | string = "testdata"
let dataList: (number | string)[] = [1, "2", 3, "four"]

 

6. Tuple

순서가 있는 서로 다른 Type이 나열되어 있는 형식

let report: [string, number] = ["math", 100]

tuple로 정의한 data는 object와는 다르게 순서가 존재합니다. array와의 차이점은, 각 index마다 type을 지정할 수 있다는 점이 있습니다. 개인적으로는 tuple보다 Array나  Object로 정의할 때가 더 많은 편이지만, Tuple은 Data의 순서가 상식으로 자리잡은 것들을 정의할 때 효과성을 발휘합니다. 예를 들면 Color(rgba)가 대표적입니다.

let ColorTuple: [number, number, number, number] = [0, 255, 255, 0.5]

let ColorObject: {red:number, green: number, blue: number, alpha: number}= {
    red: 0,
    green: 255,
    blue: 255,
    alpha: 0.5,
}

 

7. Enum

Enumerate의 약자. 열거형 타입

원래 javascript에서는 enum을 지원하지 않습니다. 그나마 Object.freeze를 사용해서 속성들의 변화를 막는 기법으로 Enum과 유사하게 사용했습니다.

const Color = Object.freeze({
  RED: 'RED',
  GREEN: 'GREEN',
  BLUE: 'BLUE',
});

console.log(Color.RED); // 출력: "RED"
console.log(Color.GREEN); // 출력: "GREEN"
console.log(Color.BLUE); // 출력: "BLUE"

 

하지만 Typescript에서는 enum을 지원합니다. 

enum Color {
  RED = 'RED',
  GREEN = 'GREEN',
  BLUE = 'BLUE',
}

console.log(Color.RED); // 출력: "RED"
console.log(Color.GREEN); // 출력: "GREEN"
console.log(Color.BLUE); // 출력: "BLUE"

 

enum을 tsc로 변환하면 다음과 같이 변환됩니다. 이는 IIFE(immediately Invoked Function Expression) 형식입니다.

var Color;
(function (Color) {
    Color["RED"] = "RED";
    Color["GREEN"] = "GREEN";
    Color["BLUE"] = "BLUE";
})(Color || (Color = {}));

console.log(Color.RED); // 출력: "RED"
console.log(Color.GREEN); // 출력: "GREEN"
console.log(Color.BLUE); // 출력: "BLUE"

 

만약 enum에 값을 지정하지 않으면 숫자를 자동으로 기입합니다. 첫 변수에 숫자를 넣으면 그 다음 변수부터는 1씩 증가하는 형태를 가집니다.

 

 - 변환 전 index.ts

enum Month {
  Jan,
  Feb,
  Mar,
  Apr,
  May,
  Jun,
  Jul,
  Aug,
  Sep,
  Oct,
  Nov,
  Dec,
}

 

 - 변환 후 index.js

var Month;
(function (Month) {
    Month[Month["Jan"] = 0] = "Jan";
    Month[Month["Feb"] = 1] = "Feb";
    Month[Month["Mar"] = 2] = "Mar";
    Month[Month["Apr"] = 3] = "Apr";
    Month[Month["May"] = 4] = "May";
    Month[Month["Jun"] = 5] = "Jun";
    Month[Month["Jul"] = 6] = "Jul";
    Month[Month["Aug"] = 7] = "Aug";
    Month[Month["Sep"] = 8] = "Sep";
    Month[Month["Oct"] = 9] = "Oct";
    Month[Month["Nov"] = 10] = "Nov";
    Month[Month["Dec"] = 11] = "Dec";
})(Month || (Month = {}));

 

8. Any

모든 타입 허용.

let result;
let result2: any;

javascript랑 다르지 않게 됩니다. 모든 유형 검사를 뛰어 넘습니다. 만약 typescript에서 변수에 type을 지정하지 않는다면 자동으로 any유형을 사용하게 됩니다.

result에는 type을 지정하지 않았더니 암시적(implicitly) any type이 지정되었다.

 

9. Void

return하지 않는 함수에 사용

function add(a: number, b: number): void {
    let c = a + b;
}

 

C언어 계열을 사용하신 분들에게 void는 익숙한 단어일 것입니다. 원래 C에서 함수가 아무것도 return하지 않으면 이렇게 적었습니다.

void ex_fn(int a, int b){
    int c = a + b;
    // no return
}

 

C에선 함수가 return하는 date type을 함수명 앞에 적게 되어있는데, 만약 아무것도 return하지 않는 함수라면 void라고 작성합니다. 이 철학을 그대로 Typescript에 적용했다 생각하면 됩니다.

 

10. Never

무한 반복하여 끝나지 않는 함수, throw만을 보내는 함수 등에 사용.

function infLoop(): never{
    while(true){
        // never end
    }
}
function errorHandling(): never{
    throw new Error("[Error] message");
}

 

함수가 아무것도 return하지 않아 void 형식을 지정하는 것과는 다르게 애초에 함수를 빠져나갈 수 없는 경우 Never를 사용합니다. vscode에서 함수 끝부분에 구문을 작성해 보면 더욱 어떤 의미인지 알 수 있습니다.

함수가 끝나는 부분이 비활성화 처리된다.

 

11. String Literal Types

문자열 값 자체를 Type을 지정. 개발 code 내에서 select처럼 사용할 일이 있을 때 유용하다.

let direction: 'left' | 'right' | 'up' | 'down' = "down"

 

약간 enum과 비슷할 수는 있지만 string literal types는 정말로 string만을 가지고 type을 지정하는 방법입니다. type과 array를 통해 더욱 깔끔하게 작성할 수 있습니다.

// type을 활용한 방법
type Direction = "left" | "right" | "up" | "down";
let direction: Direction = "up";

// Array를 활용한 방법
const colors = ["red", "blue", "green"] as const;
type Color = typeof colors[number];
let exampleColor: Color = "red";

 

Array를 활용하는 방법을 자세히 파고 들어가면(color: string[]을 쓰면 안되는 이유, as const를 써야 하는 이유, colors[number]의 의미 등) 내용이 너무 길어질 수 있어 예제만 참고하시길 바랍니다.

 

지금까지는 변수 위주로 사용했는데 이번에는 function에서 적용해보도록 하겠습니다.

 

12. function

function은 3가지 방식으로 표현될 수 있습니다. input value의 type과 ouput value의 type위치만 기억해도 충분합니다. class도 결국 method, constructor, properties의 집합이고 이는 function과 value 사용법으로도 충분히 설명이 가능하니 넘아가도록 하겠습니다.

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

const fn2 = function(a: number, b: number): number {
    return a + b;
}

const fn3 = (a: number, b: number): number => {
    return a + b;
}

// 함수 자체에 타입 지정 (함수 시그니처)
type Calculator = (x: number, y: number) => number;

const add: Calculator = (x, y) => x + y;

 

13. Generic

// function expression generic 문법
function exampleFunction<Type>(arg: Type): Type {
	return arg;
}

// arrow function generic 문법
const exampleFunction2 = <T>(arg: T):T => {
    return arg;
}

// Type 2개 이상 사용할 때 문법.
const exampleFunction3 = <T, U>(arg1: T, arg2: U): [T, U] => {
    return [arg1, arg2];
}
// Type을 명시하거나
const stringResult:string = exampleFunction<string>("test");
const numberResult:number = exampleFunction<number>(1234);

// 명시하지 않아도 사용 가능하다. 단 이렇게 작성하면 arg에 따라 function내 type이 결정된다.
const justResult:string = exampleFunction("test1234");

// tuple내에 T, U가 어떤 type으로 정해지는지는 input에 따라 결정된다.
const tupleResult = exampleFunction3("test", 1234)

 

Generic은 번역하면 '일반'입니다. 어떤 것이 일반이라는 뜻일까요? Type이 지정되지 않고 여러 Type으로 재사용 가능한 "일반"적인 함수, 클래스, 인터페이스를 작성할 때 이 "일반"이 바로 Generic입니다. Any type과는 다른 점이 모든 type을 허용하는 것이 아니라 필요한 type을 그때그때마다 적용시켜 개발하는 점이 다른 점입니다. 이렇게 적용하면 input type이 그대로 output type까지 적용되어 output의 처리를 Any Type을 사용했을 때보다는 더욱 명확해 집니다.

 

정리하자면, Generic은 function, class가 다양한 type을 사용하도록 범용성을 높이기 위해서 만들어 졌다고 볼 수 있습니다. 이런 함수의 범용성, 재활용성 문제를 겪지 않으신 분들은 generic을 사용하지 않아도 좋습니다. Generic으로 구현할 수 있는 부분은 Design pattern부분과 같은 기본 단게에서 설명할 것들은 아니기에 Generic은 여기서 설명을 마무리 하겠습니다.

 


마치며

기술을 제대로 익히기 위해서는 그 기술이 목말라야 제대로 익힐 수 있습니다. 예전의 저는 javascript로도 충분히 Application들을 개발 할 수 있었고 작은 규모였기 때문에 어떤 버그가 등장하던지 바로 잡아낼 수 있었습니다. 하지만 규모가 커지면 커질 수록 빠른 대응이 어려워졌고 그제서야 엄밀한 javascript 코딩이 필요해졌습니다. 그때서야 Typescript의 소중함이 많이 느껴졌었습니다. 이 글을 읽으시는 많은 분들도 TypeScript가 좋다라는 말만 듣고 공부했다가 동기 부여 없이 쉽게 지치시기 보다는 JavaScript만으로는 부족하다 느껴질 때 TypeScript를 시작해 보시길 바랍니다. 감사합니다.

 


* reference

https://www.w3schools.com/typescript/index.php

https://www.typescripttutorial.net/