Typescript

1.2. Typescript

1.2.1 도입 배경

Angular2 에서는 세가지 언어를 지원합니다. 우리가 잘 알고 있는 Javascript와 구글에서 만든 Dart, 그리고 이 책에서 주로 다룰 Microsoft의 Typescript 입니다. 이 세가지 언어는 동등한 레벨로 공식 가이드가 제공되고 있습니다.

애초에 구글은 Angular2를 위해 앳 스크립트(AtScript) 를 선보였습니다. 구글은 엔터프라이즈급 개발에 적합한 언어가 필요했기에, Typescript와 Javascript을 기반으로한 Script언어인 AtScript를 개발하였고, 2014년 ng-Europe 컨퍼런스에서 AtScript로 만든 Angular2 를 선보였습니다. 하지만 2015년 3월, Microsoft가 대부분의 AtScript의 요건들을 담은 Typescript 1.5 를 릴리즈하면서, Angular2는 AtScript가 아닌, 순수 Typescript로 다시 빌드 되었습니다.

이런 과정을 겪으면서까지 Angular2가 Typescript와 같은 언어를 필요로 했던 이유는 무엇일까요? 여러 이유가 있을수 있겠지만 우선 점점 엔터프라이즈화 되어가는 Front-end 개발에서 협업과 유지관리를 보다 쉽게 하고자 하는 니즈(needs) 가 있었다고 볼 수 있습니다.

그럼 Typescript의 특징과 장점은 무엇이 있을까요?

  • Typescript는 Javascript의 SuperSet 이다.
  • Javascript로 transcomplie 가능하기 때문에 브라우져 호환성이 좋다.
  • 타입(Type)을 지정하여 가독성이 좋고, 유지보수에 이점은 갖을 수 있다.
  • 객체지향(OOP) 문법을 사용할 수 있다. (ex, 상속, 인터페이스 등)
  • 객체지향 프로그래밍을 통해 로직을 구조적으로 만들 수 있다.
  • 타입 지정을 통한 정적분석이 가능하므로, 개발툴의 도움을 받을 수 있다. (ex, 코드 자동완성, 컴파일 애러 등)

1.2.2 설치 방법

npm을 통해 간단하게 Typescipt compiler(tsc) 를 설치할 수 있습니다.

$npm install -g typescipt

tsc가 제대로 설치되었는지는 tsc 명령어를 통해 확인해 볼 수 있습니다.

$tsc --version
Version 1.8.10

1.2.3 문법 특징

일반타입

Typescript은 Type anotation 을 사용합니다. 덕분에 string, number, boolean, Array 등 명시적인 자료형 선언이 가능합니다.

Boolean
let isEqual = true;
Number
let decimal: number = 6;
let hexa: number = 0xf00d;
let binary: number = 0b1010;
let octa: number = 0o744;
String
let animal: string = "pig";
animal = "dog";

console.log(animal);    // dog
Array
let animals: string[] = ["dog", "pig", "cat"];
let animals: Array<string> = ["dog", "pig", "cat"];
Any
let anyVar: any = 4;
anyVar = "assign string is ok";
anybar = true;
Void
function message(): void {
    console.log("This is message");
}

변수선언

var

var는 아래와 같이 Javascript에서 변수선언시 일반적으로 사용하는 키워드 입니다.

var color = "red";

잘 사용하면 문제가 없지만, 변수 캡쳐링(Variable Capturing) 으로 인한 문제가 발생하기도 합니다. 아래 예제를 같이 볼까요?

for (var i = 0; i < 10; i++) {
    setTimeout(function() { console.log(i); }, 100 * i);
}

대부분의 사람들은 console에 0, 1, 2, ...., 9까지 순서대로 찍힐거라고 생각하실겁니다. 하지만 실제 결과는 어떨까요?

10
10
10
10
10
10
10
10
10
10

setTimeout 후에 바라보는 i의 값이 이미 for문이 종료되고 난 뒤의 i를 바라보고 있기 때문에 전부 10으로 출력되는 것입니다. 이러한 문제를 해결하기 위해 아래와 같은 패턴을 사용하면 됩니다.

for (var i = 0; i < 10; i++) {
    (function(i) {
        setTimeout(function() { console.log(i); }, 100 * i);
    })(i);
}

시원하게 문제가 해결되었다고 생각하시나요? 대부분의 개발자들은 문제가 발생하기 전에 미리 변수 캡쳐링을 방지하고 싶을 겁니다. 그럼 어떻게하면 될까요?

let

위에서 var 선언에 대한 문제를 다뤄봤습니다. Typesciprt에서는 이런한 문제를 미연에 방지하기 위해 고안한 let 키워드가 있습니다.

let color = "red";

문맥상 var와 큰 차이는 없지만 의미적으로는 차이가 있습니다. 바로 Block-scoping 이 가능하다는 것입니다. Block-scoping 은 block을 기준으로 변수의 scope을 결정합니다. 즉, block 안에서 선언한 변수는 block 밖에서는 사용이 불가능합니다.

function f(input: boolean) {
    let a = 10;

    if (input) {
        // 바깥에서 선언한 a를 접근할 수 있다.
        let b = a + 10;
        return b;
    }

    // Error: 블럭(block)안에서 선언된 b 변수에 접근할 수 없다.
    return b;
}

변수를 선언하기 전에 사용하는 것도 허용되지 않습니다.

a += 10;    // Error
let a;

또한 let을 사용하면 var선언 부분에서 언급했던 변수 캡쳐링으로 인한 문제를 제어할 수 있습니다.

for (let i = 0; i < 10; i++) {
    setTimeout(function() { console.log(i); }, 100 * i);
}

결과는 우리가 기대했던 0, 1, 2, 3, ... , 9 가 순서대로 출력됩니다.

const

const는 java에서와 마찬가지로 상수를 선언할때 사용됩니다. 즉, 한번 const로 선언된 변수는 다른 값으로 치환되지 않습니다.

const ageForPerson = 30;
const person = {
    name: "Yoon",
    age: ageForPerson
}

// Error
ageForPerson = 15;

// Error
person = {
    name: "Kim",
    age: ageForPerson
};

// Ok
person.name = "Park";
person.name = "Sun";
person.name = "Jeon";
person.age--;

Class

본래 Javascript는 Class 기반의 객체지향 프로그래밍이 불가능합니다. 대신 function과 prototype 기반 의 객체지향 프로그래밍이 가능하였는데, 이는 우리가 Java에서 사용하는 Class 사용방식과 비교하면 굉장히 어색하고 직관적이지 않습니다. 다행스럽게도 ECMAScrip 6 의 스팩에 Class 개념이 포함되었고, Typescript는 이를 사용할 수 있도록 구현해 놓았습니다. 간단하게 Class를 구현해볼까요?

class Person {
    name: string;

    constructor(name: string) {
        this.name = name;
    }
    getName(): string {
        return "I am " + this.name;
    }
}

let onePerson = new Person("Yoon");

Java로 class를 구현해 보았다면, 문법이 거의 유사함을 느끼실 수 있을것 같습니다. class 를 구현하였다면 이제 상속(Inheritance) 도 구현해 볼까요?

class Man extends Person {
    age: number;

    constructor(name: string, age: number) {
        super(name);
        this.age = age;
    }

    getAge(): string {
        return "I am " + this.age + " years old";
    }
}

이 외에도 Java에서 사용하는 public, private, protected 와 같은 수정자도 사용 가능합니다.

Interface

Typescript의 핵심 원칙 중 하나는 바로 그것이 가지고 있는 모양(value)으로부터 타입을 체크한다는 것입니다. 이를 덕 타이핑(Duck typing) 이라고도 하는데, 무슨 말인지는 앞으로 진행될 예제를 통해 설명해 드리도록 하겠습니다. Typescript에서는 이를 기반으로 강력한 Interface 기능을 구현했습니다. Typescript가 가지고 있는 다양한 Interface 의 기능들을 확인해 볼까요?

interface PersonValue {
    name: string;
    age: number;
}

function printPersonInfo(personInfo: PersonValue) {
    console.log(personInfo.name, personInfo.age);
}

let personInfo = {name: "Yoon", age: 30};
printPersonInfo(personInfo);

위의 예제에서 interface PersonValueprintPersonInfo에 들어갈 인자의 타입 형태를 정의하고 있습니다. printPersonInfo에는 string, number가 포함된 Object를 넘겨달라는 규칙을 정하게 된 것입니다. 그럼 만약에 PersonValue의 규칙에 어긋나는 형태가 들어오면 어떻게 될까요?

let personInfo2 = {name: "Kim"};
printPersonInfo(personInfo2);  // Error

위의 예제에서 printPersonInfo(personInfo2); 는 컴파일 에러가 발생하는데, 이유는 {name: string} object에 '{age: number}' 가 없기 때문입니다. 신기하게도 interface PersonValue를 구체화한 Object를 인자로 넘기지 않아도 마치 같은 타입이 넘어온 것 처럼 인식하는데, 이러한 타입체킹이 처음에 언급했던 덕 타이핑 입니다. 모양(value)에 들어있는 타입과 행동이 Interface에서 정의한 바와 같으면 이는 곧 같은 타입이다 라고 인식한다고 생각하시면 될 거 같습니다.

Java에 익숙한 독자라면 Interface는 class에 implements 형태로 사용하는게 아닌가? 라고 생각하고 계실겁니다. 실제로 그렇게도 사용이 가능합니다. 아래 예제를 통해 확인해 볼까요?

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

interface에서 명세한 구조를 class Clock 에서 구체화해야 한다는 점이 Java에서의 interface의 역할과 같음을 확인할 수 있습니다. 만약에 interface ClockInterface에서 명세한 변수와 함수를 class Clock에서 구체화하지 않으면 컴파일시 애러가 발생합니다.

results matching ""

    No results matching ""