타입스크립트 (v2.7)

typescript, study, share · 2018-10-15

← 리스트로

typescript 핸드북 (v2.7)

기본타입

Boolean

Number

String

Array

Tuple - 고정된 개수의 요소타입을 알고 있지만 반드시 같을 필요는 없는 배열을 표현

  • 일반적 사용

    // 튜플 타입 선언
    let x: [string, number];
    
    x = ["hello", 10]; // 좋아요
    
    x = [10, "hello"]; // 오류
    
  • 타입 정의가 안된 인덱스의 사용시 유니온 타입이 적용됨

Enum 열거

enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;

Any

let notSure: any = 4;
notSure.ifItExists(); // 좋아요, 런타임에 ifItExists가 존재할 수 있습니다.
notSure.toFixed(); // 좋아요, toFixed는 존재합니다. (그러나 컴파일러는 체크하지 않습니다)

let prettySure: Object = 4;
prettySure.toFixed(); // 오류: 'Object' 타입에 'toFixed' 프로퍼티는 존재하지 않습니다.

void

  • undefined or null만 할당할 수 있음
function warnUser(): void {
    alert("This is my warning message");
}

null undefined

  • 자체적인 타입을 가짐 (유용하지 않음 비추천한다고 함)
let u: undefined = undefined;
let n: null = null;

Never

  • 리턴을 하지 않음을 보장해야 할때 (에러를 던지거나 종료가 안되는 메서드일때)

Type assertions

  • 타입 캐스팅과 비슷하지만 특별한 검사나 데이터 재구성은 하지 않는다.
let strLength: number = (<string>someValue).length;
// or
let strLength: number = (someValue as string).length;

변수선언

let, const

비구조화 할당

  • 객체 비구조화일 경우 다음과 같은 상황에서 조금 혼란스러울 수 있다.
let { a, b }: { a: string, b: number } = o;

전개 연산자

- 메서드는 전개연산에 포함되지 않음.

인터페이스

  • 객체의 세계에서 타입 계약을 정의하는 강력한 방법

첫번째 인터페이스

interface LabelledValue {
  label: string;
}

function printLabel(labelledObj: LabelledValue) {
  console.log(labelledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);

선택적 프로퍼티

  • 필수가 아닌 프로퍼티의 표시
interface SquareConfig {
  color?: string;
  width?: number;
}

읽기전용 프로퍼티

interface Point {
  readonly x: number;
  readonly y: number;
}

let ro: ReadonlyArray<number> = a;

readonly vs const

  • 변수는 const를 사용하는 반면 프로퍼티는 readonly를 사용합니다

프로퍼티 초과 검사 (Excess Property Checks)

  • 정의되지 않는 프로퍼티는 에러를 뱉어내게 한다
  • 인덱스 시그니처를 추가하면 예외가 되는 추가 프로퍼티를 정의할 수 있다.
  • 타입 단언을 사용하면 에러를 피할 수 있다.
  • 변수에 할당하여 인자로 넘기면 에러를 피할 수 있다.
  • 하지만 되도록이면 이러한 시도를 하지 말아햐 한다.

함수타입

  • 인터페이스는 함수의 타입도 정의할 수 있다(매개 변수와 반환타입만 주어진 함수 선언과 같다).

    interface SearchFunc {
        (source: string, subString: string): boolean;
    }
    
    let mySearch: SearchFunc;
    mySearch = function(source: string, subString: string) {
        let result = source.search(subString);
        return result > -1;
    };
    
  • 함수 타입의 타입을 검사할 때 매개 변수의 이름이 일치할 필요는 없다.

    let mySearch: SearchFunc;
    mySearch = function(src: string, sub: string): boolean {
        let result = src.search(sub);
        return result > -1;
    };
    

인덱싱 가능 타입(Indexable Types)

  • 괄호 표기법으로 객체의 프로퍼티에 동적으로 접근해야 할 때 필요하다.
  • 만약 문자열 색인과 숫자 색인이 모두 존재하는 경우, 숫자로 색인 된 값의 타입은 문자열로 색인 된 값 타입의 서브타입이어야 한다.
interface StringArray {
    [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];
  • readonly로 만들수도 있다.
interface ReadonlyStringArray {
    readonly [index: number]: string;
}

클래스 타입

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 ClockConstructor {
        new (hour: number, minute: number): ClockInterface;
    }
    interface ClockInterface {
        tick();
    }
    
    function createClock(
        ctor: ClockConstructor,
        hour: number,
        minute: number
    ): ClockInterface {
        return new ctor(hour, minute);
    }
    
    class DigitalClock implements ClockInterface {
        constructor(h: number, m: number) {}
        tick() {
            console.log("beep beep");
        }
    }
    class AnalogClock implements ClockInterface {
        constructor(h: number, m: number) {}
        tick() {
            console.log("tick tock");
        }
    }
    
    let digital = createClock(DigitalClock, 12, 17);
    let analog = createClock(AnalogClock, 7, 32);
    

인터페이스 확장

  • 인터페이스 재사용
interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

하이브리드 타입 인터페이스

  • 다음과 같이 추가 프로퍼티로 함수와 객체 역할을 모두 하는 객체를 만들 수 있다.

    interface Counter {
      (start: number): string;
      interval: number;
      reset(): void;
    }
    
    function getCounter(): Counter {
      let counter = <Counter>function(start: number) {}; counter.interval = 123;
      counter.reset = function() {};
      return counter;
    }
    
    let c = getCounter();
    c(10);
    c.reset();
    c.interval = 5.0;
    

인터페이스 확장 클래스

  • 인터페이스는 기본 클래스의 private 및 protected 멤버조차도 상속한다.

    class Control {
      private state: any;
    }
    
    interface SelectableControl extends Control {
      select(): void;
    }
    
    class Button extends Control implements SelectableControl {
      select() {}
    }
    

클래스

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

let greeter = new Greeter("world");

상속

class Animal {
    name: string;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

class Snake extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 5) {
        console.log("Slithering...");
        super.move(distanceInMeters);
    }
}

class Horse extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 45) {
        console.log("Galloping...");
        super.move(distanceInMeters);
    }
}

let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");

sam.move();
tom.move(34);

Public, private, 그리고 protected 지정자

  • 기본적인 Public (생략하면 기본적으로 public이며 그래도 명시할 수 있다.)

    class Animal {
        public name: string;
        public constructor(theName: string) { this.name = theName; }
        public move(distanceInMeters: number) {
            console.log(`${this.name} moved ${distanceInMeters}m.`);
        }
    }
    
  • private

  • protected

Readonly 지정자

class Octopus {
    readonly name: string;
    readonly numberOfLegs: number = 8;
    constructor (theName: string) {
        this.name = theName;
    }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 오류! name은 readonly입니다.

매개변수 프로퍼티

  • 매개변수의 선언과 할당을 하나의 장소로 통합할 수 있다.

  • 마찬가지로 public와 protected 그리고 readonly도 마찬가지입니다.

    class Octopus {
        readonly numberOfLegs: number = 8;
        constructor(readonly name: string) {
        }
    }
    

접근자 (Accessors)

  • getters/setters 를 지원

정적 프로퍼티

class Grid {
    static origin = {x: 0, y: 0};
    calculateDistanceFromOrigin(point: {x: number; y: number;}) {
        let xDist = (point.x - Grid.origin.x);
        let yDist = (point.y - Grid.origin.y);
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    constructor (public scale: number) { }
}

let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scale

console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

추상 클래스

  • 직접적으로 인스턴스화 할 수 없는 다른 클래스가 파생될 수 있는 기본 클래스.

  • 내부의 추상 메서드를 정의할 수 있다.

    abstract class Department {
    
        constructor(public name: string) {
        }
    
        printName(): void {
            console.log("Department name: " + this.name);
        }
    
        abstract printMeeting(): void; // 파생된 클래스에서 구현해야 합니다.
    }
    

고급 기법

  • 인스턴스 타입으로 사용할 수 있다.

  • 클래스의 스테틱한 측면을 활용할 수 있다.

  • 클래스를 인터페이스로 사용할 수 있다.

    class Point {
        x: number;
        y: number;
    }
    
    interface Point3d extends Point {
        z: number;
    }
    
    let point3d: Point3d = {x: 1, y: 2, z: 3};
    
    

함수

  • 함수는 JavaScript의 모든 애플리케이션을 구성하는 기본 요소입니다.

  • 기명함수익명함수로 만들 수 있다.

  • 선택적으로 반환값을 생략할 수 있다 (리턴문을 보고 타입을 파악 가능하기 때문에)

    function add(x: number, y: number): number {
        return x + y;
    }
    
    let myAdd = function(x: number, y: number): number { return x + y; };
    

함수 타입 작성하기

  • 캡처된 변수는 타입에 반영되지 않습니다.
let myAdd: (baseValue: number, increment: number) => number =
    function(x: number, y: number): number { return x + y; };

타입 추론 - 선언부에 타입이 명시되었다면 구현부에는 타입을 생략할 수 있다.

// myAdd는 완벽하게 함수 타입을 가지고 있습니다.
let myAdd = function(x: number, y: number): number { return  x + y; };

// 매개변수 'x'와 'y'에는 number 타입이 있습니다.
let myAdd: (baseValue: number, increment: number) => number =
    function(x, y) { return x + y; };

선택적 매개변수와 기본 매개변수

  • 함수에 주어진 인수의 수는 그 함수에서 기대하는 매개변수의 수와 일치해야 합니다.
function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");                  // 오류, 너무 적은 매개변수
let result2 = buildName("Bob", "Adams", "Sr.");  // 오류, 너무 많은 매개변수
let result3 = buildName("Bob", "Adams");         // 아, 딱 맞습니다
  • 매개변수를 선택적으로 사용하도록 할 수 있다.
function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}

let result1 = buildName("Bob");                  // 올바르게 작동합니다
let result2 = buildName("Bob", "Adams", "Sr.");  // 오류, 너무 많은 매개변수
let result3 = buildName("Bob", "Adams");         // 아, 딱 맞습니다

나머지 매개변수

function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + " " + restOfName.join(" ");
}

let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;

this와 화살표 함수

  • this로 인해 혼란스럽기 때문에 this타입을 정확히 명시하여 혼란을 피할 수 있다.
interface Card {
    suit: string;
    card: number;
}
interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),

    // 주의사항 : 이 함수는 이제 반드시 Deck 타입이어야합니다
    createCardPicker: function(this: Deck) {
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

콜백에서의 this 매개변수

  • 화살표 함수를 사용하면 이벤트 메서드의 this오류를 해결할 수 있다.
class Handler {
    info: string;
    Event) => { this.info = e.message }
}

오버 로드

  • 함수를 오버로드 방식으로 구현 가능하다.
let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
    // 객체 / 배열로 작업하고 있는지 확인해보세요
    // 그렇다면 그것들은 덱을 주고 사용자는 카드를 선택할 것입니다.
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // 그렇지 않으면 카드를 선택하게하세요.
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

제네릭

  • 다양한 타입을 처리할수 있도록 하는 컴포넌트를 만든다.
function identity<T>(arg: T): T {
    return arg;
}

let output = identity<string>("myString");  // 반환 타입은 'string' 입니다.
  • 타입 인수 추론(type argument inference) 를 사용
let output = identity("myString");  // 반환 타입은 'string' 입니다.

제네릭 타입 변수

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // 오류 : T는 .length 메소드를 가지고 있지 않습니다.
    return arg;
}


function loggingIdentity<T>(arg: Array<T>): Array<T> {
    console.log(arg.length);  // Array는 .length 멤버가 있습니다. 오류 없음.
    return arg;
}

제네릭 타입

  • 제네릭 타입의 인터페이스를 만드는 방법
function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: <T>(arg: T) => T = identity;

let myIdentity: <U>(arg: U) => U = identity;

let myIdentity: {<T>(arg: T): T} = identity;
interface GenericIdentityFn {
    <T>(arg: T): T;
}

let myIdentity: GenericIdentityFn = identity;
interface GenericIdentityFn<number> {
    (arg: T): T;
}

let myIdentity: GenericIdentityFn<number> = identity;

제네릭 클래스

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
  • 클래스의 정적 맴버는 타입 매개변수를 사용할 수 없다.

제네릭 제약조건

  • length 프로퍼티를 가진 모든 타입에 대해 제네릭을 정의하고 싶을 경우 아래와 같이 할 수 있다.
interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // 이제 .length 프로퍼티가 있으므로 더이상 오류가 없습니다.
    return arg;
}

loggingIdentity(3);  // 오류, number 는 .length 프로퍼티가 없습니다.

제네릭 제약조건에서 타입 매개변수 사용

function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a"); // 오류 없음
getProperty(x, "m"); // 오류 : 타입 'm'의 인수를 'a' | 'b' | 'c' | 'd' 에 할당 할 수 없습니다.

제네릭에서 클래스 타입 사용

class BeeKeeper {
    hasMask: boolean;
}

class ZooKeeper {
    nametag: string;
}

class Animal {
    numLegs: number;
}

class Bee extends Animal {
    keeper: BeeKeeper;
}

class Lion extends Animal {
    keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
    return new c();
}

createInstance(Lion).keeper.nametag;  // 타입 체크!
createInstance(Bee).keeper.hasMask;   // 타입 체크!

열거형 (Enums)

enum Direction {
    Up = 1,
    Down,
    Left,
    Right,
}
  • 계산된 멤버를 사용할 수 있지만, 다음은 허용되지 않는다.

    enum E {
        A = getSomeValue(),
        B, // 오류 ! A 는 상수로 초기화되지 않았으므로 B에는 초기화가 필요합니다.
    }
    

문자 열거형

  • 자동증가 동작을 하지 않지만 직렬화에 대한 이점이 있다. (상수에 대한 의미를 알아내는데 도움이 된다.)

    enum Direction {
        Up = "UP",
        Down = "DOWN",
        Left = "LEFT",
        Right = "RIGHT",
    }
    

별종 열거형

  • 이렇게 사용하지 않는 것이 좋다.
    enum BooleanLikeHeterogeneousEnum {
        No = 0,
        Yes = "YES",
    }
    

계산된 상수 멤버

  • 아래 나열된 경우가 아닌경우 전부 계산된 상수로 여긴다.

    1. 리터럴 열거 표현식 (기본적으로 문자 리터럴 또는 숫자 리터럴)
    2. 이전에 정의된 상수 열거형 멤버 (다른 열거형에서 올 수 있음)에 대한 참조
    3. 괄호로 묶인 상수 열거형 표현식
    4. 상수 열거형 표현식에 적용된 +, -, ~ 단항 연산자 중 하나
    5. +, -, *, /, %, <<, >>, >>>, &, |, ^ 바이너리 연산자에 사용된 상수 열거형 표현식 상수 열거형 표현식이 NaN 또는 Infinity 로 평가되는 것은 컴파일 타임 에러입니다.
    enum FileAccess {
        // 상수 멤버
        None,
        Read    = 1 << 1,
        Write   = 1 << 2,
        ReadWrite  = Read | Write,
        // 계산된 멤버
        G = "123".length
    }
    

통합 열거형 및 열거형 멤버 타입

  • 열거형의 멤버도 타입으로 여겨진다.

    enum ShapeKind {
        Circle,
        Square,
    }
    
    interface Circle {
        kind: ShapeKind.Circle;
        radius: number;
    }
    
    interface Square {
        kind: ShapeKind.Square;
        sideLength: number;
    }
    
    let c: Circle = {
        kind: ShapeKind.Square,
        //    ~~~~~~~~~~~~~~~~ 오류!
        radius: 100,
    }
    

런타임시의 열거형

  • 열거형은 런타임에 존재하는 실제 객체이다.

    enum E {
        X, Y, Z
    }
    
    function f(obj: { X: number }) {
        return obj.X;
    }
    
    // 작동합니다. 왜냐하면 `E` 는 숫자인 `X` 라는 속성을 가지고있기 떄문입니다.
    f(E);
    

역 매핑 가능

enum Enum {
    A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

const 열거형을 사용하면 컴파일 후 필요없는 상수 표현은 없어지지만 버그를 추척하기 어려워진다.

앰비언트 열거형 ?? 몰르겠음

타입추론

  • 변수와 멤버를 초기화하거나 매개 변수의 기본값을 설정하거나 함수 반환 타입을 결정할때 발생한다

일반적 타입

  • 가장 일반적인 타입을 명시

    let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];
    
  • 일반적 타입이 발견되지 않으면 유니온 타입으로 추론된다. ((Rhino | Elephant | Snake)[])

    let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];

상황적 타입

  • 일반적 추론이 안되는 경우 타입스크립트는 표현식의 위치에 따라 상황적 으로 타입을 추론하지만 부적확 할 수 있다.

  • 아래와 같이 타입 단언을 통하여 오류를 방지할수 있지만 되도록이면 any나 as는 적게 사용하는 것이 좋다.

    window.onmousedown = function(mouseEvent: any) {
        console.log(mouseEvent.button);  //<- 이제 오류가 없습니다
    };
    

타입 호완성

  • 멤버에 따라 타입이 호완된다.

    • 이름이 틀리더라도 멤버가 같다면 동일한 타입으로 호환될 수 있다.
    interface Named {
        name: string;
    }
    
    class Person {
        name: string;
    }
    
    let p: Named;
    // 구조적 타이핑이니까 좋아요 
    p = new Person();
    

함수의 호환성

let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // 좋아요
x = y; // 오류
  • 함수 매개변수의 Bivariance

    • 상위타입으로 명시된 인자로 더 구체적인 타입을 받아서 처리 가능하지만 추천하지 않음
  • 선택적 매개 변수와 나머지 매개 변수

    • 필수 매개변수와 선택적, 선택적 매개변수는 호환된다.
    • 나머지 매개 변수는 무한대의 선택적 매개변수인 것 것처럼 처리된다.

열거형 (Enums)

  • 숫자형과 열거형은 서로 호환된다.
  • 다른 열거형에서 가져온 열거형은 호환되지 않는다.
enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };

let status = Status.Ready;
status = Color.Green;  // 오류

클래스 (Classes)

  • 한 클래스 타입의 두 객체를 비교할 때 인스턴스의 멤버만 비교됩니다.
  • 정적 멤버 및 생성자는 호환성에 영향을 미치지 않습니다.
  • 클래스의 Private와 protected 멤버가 동일하게 적용되어 있어야 호환된다.

제네릭 (Generics)

  • 타입의 유형이 멤버 유형으로 쓰일때만 영향을 준다.

    interface NotEmpty<T> {
        data: T;
    }
    let x: NotEmpty<number>;
    let y: NotEmpty<string>;
    
    x = y;  // 오류, x와 y는 호환되지 않습니다
    
  • 형식이 지정되지 않은 제너릭의 경우 모두 호환된다.

    let identity = function<T>(x: T): T {
        // ...
    }
    
    let reverse = function<U>(y: U): U {
        // ...
    }
    
    identity = reverse;  // 좋아요 왜냐하면 (x: any)=>any 일치 (y: any)=>any
    

고급타입

심볼

  • 자료구조에 익명을 부여하여 사용할수 있다.

ECMAScript 2015에서 시작된 symbol은 number와 string같은 primitive 데이터 타입입니다.

이터레이터와 제네레이터

Symbol.iterator 프로퍼티에 대한 구현을 하고 있는 객체는 iterable로 간주됩니다. Array, Map, Set, String, Int32Array, Uint32Array 등과 같은 몇몇 내장 타입은 이미 구현된 Symbol.iterator 프로퍼티를 가지고 있습니다.

모듈

내보내기

  • 내보내기 선언
export const numberRegexp = /^[0-9]+$/;

export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
  • 이름 변경하여 내보내기
export { ZipCodeValidator as mainValidator };
// or
export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";
  • default 내보내기
export default class ZipCodeValidator {
    static numberRegexp = /^[0-9]+$/;
    isAcceptable(s: string) {
        return s.length === 5 && ZipCodeValidator.numberRegexp.test(s);
    }
}
import validator from "./ZipCodeValidator";

let myValidator = new validator();

가져오기

  • 모듈에서 단일 내보내기 가져오기
import { ZipCodeValidator } from "./ZipCodeValidator";

let myValidator = new ZipCodeValidator();
  • 전체 모듈을 단일 변수로 가져오기
import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();
  • 부수 효과에 대한 모듈만 가져오기
    • 권장되지는 않지만 일부 모듈은 다른 모듈에서 사용할 수 있는 글로벌 상태를 설정합니다.
import "./my-module.js";

기존의 CommonJS와 AMD 워크플로우를 모델링

  • TypeScript는 기존의 CommonJS와 AMD 워크플로우를 모델링하기 위해 export =를 지원합니다.
// 내보내기
let numberRegexp = /^[0-9]+$/;
class ZipCodeValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
export = ZipCodeValidator;

// 가져오기
import zip = require("./ZipCodeValidator");

모듈을 위한 코드 생성

  • 타입스크립트 컴파일시 환경 시스템에 적절한 모듈 로드 코드로 적절하게 변환시켜 준다. 아래는 umd의 경우 예 이다.
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports); if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./mod"], factory);
    }
})(function (require, exports) {
    var mod_1 = require("./mod");
    exports.t = mod_1.something + 1;
});
  • 사용하려는 환경에 따라 다른 커맨드를 이용하여 컴파일 하여야 한다.
    1. Node.js는 --module commonjs를 사용하세요.
    2. require.js의 경우 --module amd를 사용합니다.
tsc --module commonjs Test.ts

컴파일시 각 모듈은 별도의 .js 파일이 됩니다.

선택적 모듈 로딩과 기타 고급 로딩 시나리오

  • 상황에 따라 일부 조건에서만 모듈을 로드 할 수 있다.
  • 모듈 식별자가 타입 어노테이션의 일부로만 사용되고 표현식으로 사용되지 않으면 해당 모듈에 대한 require 호출이 방출하지 않습니다. 사용하지 않는 참조를 제거하면 성능이 최적화되고 해당 모듈을 선택적으로 로드할 수 있습니다.
  • 이 패턴이 작동하려면 import를 통해 정의된 symbol이 타입 위치에서만 사용되어야 합니다
declare function require(moduleName: string): any;

import { ZipCodeValidator as Zip } from "./ZipCodeValidator";

if (needZipValidation) {
    let ZipCodeValidator: typeof Zip = require("./ZipCodeValidator");
    let validator = new ZipCodeValidator();
    if (validator.isAcceptable("...")) { /* ... */ }
}

다른 JavaScript 라이브러리 사용

TypeScript로 작성되지 않은 라이브러리의 형태을 설명하려면 라이브러리가 나타내는 API를 선언해야 합니다. 구현을 "ambient"으로 정의하지 않는 선언이라고 하며 일반적으로 이들은 .d.ts 파일에 정의되어 있습니다. C/C++에 익숙하다면 이것들을 .h파일로 생각할 수 있을 것입니다.

  • 엠비언트란 구현을 알지 못하는 모듈(외부모듈)의 형식을 지정하는것을 말한다.
  • **엠비언트 네임스페이스 사용 **
    • declare 로 reference 지시자를 이용하여 모듈을 사용할 수 있다.
declare module "url" {
    export interface Url {
        protocol?: string;
        hostname?: string;
        pathname?: string;
    }

    export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
}

declare module "path" {
    export function normalize(p: string): string;
    export function join(...paths: any[]): string;
    export var sep: string;
}

/// <reference path="node.d.ts"/>
import * as URL from "url";
let myUrl = URL.parse("http://www.typescriptlang.org");
  • 엠베안트 모듈의 쇼트컷
    • shorthand 모듈의 모든 imports는 any 타입을 가집니다.
declare module "hot-new-module";
  • UMD modules
    • 전역 네임스페이스를 사용하는 모듈들은 다음과 같이 선언하여 사용할 수 있다.
export function isPrime(x: number): boolean;
export as namespace mathLib;

import { isPrime } from "math-lib";
isPrime(2);
mathLib.isPrime(2); // 오류: 모듈 내부에서 전역 정의를 할 수 없습니다.

모듈 구조화를 위한 가이드

  • 최 상위 레벨에 가깝게 내보내기 (중첩이 많은 상태는 안좋다)
  • 단일 클래스나 함수일 경우 export default 를 사용하는것이 좋다.
  • 다수의 객체를 내보내는 경우 모두 최상위 레벨에 배치하세요. - 네임드 import 사용
  • 모듈에서 네임스페이스를 사용하지 말아라.
  • 전역의 세계에서는 네임스페이스가 중요하지만 모듈의 세계에서는 네임스페이스를 딱히 사용할 이유가 없다.

네임스페이스

  • 내부 모듈을 정의하는 방법
namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }

    const lettersRegexp = /^[A-Za-z]+$/;
    const numberRegexp = /^[0-9]+$/;

    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }

    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}

// 시험용 샘플
let strings = ["Hello", "98052", "101"];

// 사용할 Validators
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

// 각 문자열이 Validator를 통과했는지 여부를 보여 줍니다.
for (let s of strings) {
    for (let name in validators) {
        console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
    }
}
  • 하나의 네임스페이스를 여러개의 파일로 나누어서 관리 가능하다.
  • 단일 파일로 컴파일 하기
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />

// 시험용 샘플
let strings = ["Hello", "98052", "101"];

// 사용할 Validators
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();

// 각 문자열이 Validator를 통과했는지 여부를 보여 줍니다.
for (let s of strings) {
    for (let name in validators) {
        console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
    }
}
tsc --outFile sample.js Test.ts

별칭

namespace Shapes {
    export namespace Polygons {
        export class Triangle { }
        export class Square { }
    }
}

import polygons = Shapes.Polygons;
let sq = new polygons.Square(); // 'new Shapes.Polygons.Square()'와 동일합니다

다른 JavaScript 라이브러리로 작업하기

  • D3.js처럼 모듈로더가 없고 전역 객체에서 기능을 정의하는 경우 Namespace를 통해 기능을 정의할 수 있다.
  • Ambient Namespaces(네임 스페이스를 활용한 정의하지 않은 선언을 호출)
declare namespace D3 {
    export interface Selectors {
        select: {
            (selector: string): Selection;
            (element: EventTarget): Selection;
        };
    }

    export interface Event {
        x: number;
        y: number;
    }

    export interface Base extends Selectors {
        event: Event;
    }
}

declare var d3: D3.Base;

네임스페이스 와 모듈

네임스페이스와 모듈을 사용하는데 흔히 발생하는 실수와 위험에 대한 주제를 다룬다.

  1. 일반적인 실수는 import 문을 사용하는 대신 모듈 파일을 참조하기 위해 /// <reference … /> 문을 사용하는 것이다.
    • /// <reference … />를 사용하기 위해서는 모듈형태를 .d.ts 파일에 선언해야 한다는 것을 기억해야 합니다.
  2. 불필요한 네임스페이스
    • 모듈의 사용자가 할당할 이름을 결정하기 때문에 네임스페이스에서 내보낸 심볼을 사전에 감쌀 필요가 없습니다.
export namespace Shapes {
    export class Triangle { /* ... */ }
    export class Square { /* ... */ }
}

모듈 해석

선언 병합

인터페이스 병합

네임스페이스 병합

클래스, 함수 그리고 열거형 병합

- 클래스와 네임스페이스와의 병합은 클래스의 정적 프로퍼티로 병합된다.

허용되지 않는 병합

- 클래스는 다른 클래스 또는 변수와 병합할 수 없습니다. (믹스인 패턴으로 병합해야 한다)

모듈확대 또는 전역확대

// observable.ts는 그대로 유지됩니다.
// map.ts
import { Observable } from "./observable";
declare module "./observable" {
    interface Observable<T> {
        map<U>(f: (x: T) => U): Observable<U>;
    }
}
Observable.prototype.map = function (f) {
    // ... 또 다른 숙제
}


// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map(x => x.toFixed());

JSX

기본 사용 방법

  1. 파일 이름을 .tsx 확장자로 지정하라.
  2. jsx 옵션을 활성화 하라.
  • typescript에서는 세가지 jsx모드가 있다 (preserve, react, react-native)

as 연산자

  • 타입단언의 괄호 꺽쇠가 jsx의 태그와 겹치기 때문에 as를 사용하여 타입단언을 한다.

데코레이터

  • 실험적인 기능이다
  • 어노테이션, 메타-프로그래밍 구문을 모두 추가할 수 있는 기능
  • 설정 활성화
    {
        "compilerOptions": {
            "target": "ES5",
            "experimentalDecorators": true
        }
    }
    

클래스 데코레이터

  • 생성자 함수를 인수로 받아 가로채어 사용할 수 있다.

    function sealed(constructor: Function) {
        Object.seal(constructor);
        Object.seal(constructor.prototype);
    }
    
  • 생성자를 재정의 하는 예제

    function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
        return class extends constructor {
            newProperty = "new property";
            hello = "override";
        }
    }
    

메서드 데코레이터

  • 메서드 정의를 관찰, 수정 또는 바꾸는 데 사용할 수 있습니다.

    function enumerable(value: boolean) {
        return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
            descriptor.enumerable = value;
        };
    }
    

매개변수 데코레이터

프로퍼티 데코레이터

믹스인

  • 클래스를 결함하여 새로운 클래스를 구축함

    // Disposable 믹스인
    class Disposable {
        isDisposed: boolean;
        dispose() {
            this.isDisposed = true;
        }
    
    }
    
    // Activatable 믹스인
    class Activatable {
        isActive: boolean;
        activate() {
            this.isActive = true;
        }
        deactivate() {
            this.isActive = false;
        }
    }
    
    class SmartObject implements Disposable, Activatable {
        constructor() {
            setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500);
        }
    
        interact() {
            this.activate();
        }
    
        // Disposable
        isDisposed: boolean = false;
        dispose: () => void;
        // Activatable
        isActive: boolean = false;
        activate: () => void;
        deactivate: () => void;
    }
    applyMixins(SmartObject, [Disposable, Activatable]);
    
    let smartObj = new SmartObject();
    setTimeout(() => smartObj.interact(), 1000);
    
    ////////////////////////////////////////
    // 런타임 라이브러리 어딘가에
    ////////////////////////////////////////
    
    function applyMixins(derivedCtor: any, baseCtors: any[]) {
        baseCtors.forEach(baseCtor => {
            Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
                derivedCtor.prototype[name] = baseCtor.prototype[name];
            });
        });
    }
    

트리플-슬래시 지시자

  • xml 태그가 포함된 한줄주석
  • 트리플-슬래시 지시자는 포함된 파일의 상단에서만 유효합니다

JavaScript 파일 타임 검사

TypeScript 2.3 및 이후 버전에서는 --checkJs를 사용하여 .js 파일에서 타입 검사 및 오류 보고 모드를 지원합니다.

JSDoc에서 타입 사용 (Using types in JSDoc)

/** @type {number} */
var x;

x = 0;      // 좋아요
x = false;  // 오류: number에 boolean을 할당할 수 없습니다

CommonJS 모듈 입력 지원

// import module "fs"
const fs = require("fs");


// export function readFile
module.exports.readFile = function(f) {
    return fs.readFileSync(f);    
}

타입 지정

/** @type {{a: number}} */
var obj = { a: 1 };
obj.b = 2;  // 오류, {a: number} 타입은 b 프로퍼티를 가지고 있지 않습니다.