2014년 6월 15일 일요일

TypeScript 문법 특징 (Modules, Functions, Generics)

JavaScript 문법이나 자세한 내부적인 동작들을 알고 TypeScript 문법을 보면 비교를 해보면서 좋겠지만.. 일단 guide 그대로 보고 정리함.
(원문) http://www.typescriptlang.org/Handbook

[Modules]

module 내부에서 정의된 methods들을 외부에서 사용하려면 export keyword를 사용해야 함.

module Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }

    var lettersRegexp = /^[A-Za-z]+$/;
    var 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);
        }
    }
}

// Some samples to try
var strings = ['Hello', '98052', '101'];
// Validators to use
var validators: { [s: string]: Validation.StringValidator; } = {};
validators['ZIP code'] = new Validation.ZipCodeValidator();
validators['Letters only'] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
    for (var name in validators) {
        console.log('"' + s + '" ' + (validators[name].isAcceptable(s) ? ' matches ' : ' does not match ') + name);
    }
});

[Multi-file internal modules]

독립된 파일을 export해서 사용하는 방법.
/// <reference path="xxxx.ts" /> 를 명시하여 다른 파일에 존재하지만 컴파일러에서는 한 파일로 구성된것으로 취급

Validation.ts
module Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }
}
LettersOnlyValidator.ts
/// <reference path="Validation.ts" />
module Validation {
    var lettersRegexp = /^[A-Za-z]+$/;
    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return lettersRegexp.test(s);
        }
    }
}
ZipCodeValidator.ts
/// <reference path="Validation.ts" />
module Validation {
    var numberRegexp = /^[0-9]+$/;
    export class ZipCodeValidator implements StringValidator {
        isAcceptable(s: string) {
            return s.length === 5 && numberRegexp.test(s);
        }
    }
}
Test.ts
/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />

// Some samples to try
var strings = ['Hello', '98052', '101'];
// Validators to use
var validators: { [s: string]: Validation.StringValidator; } = {};
validators['ZIP code'] = new Validation.ZipCodeValidator();
validators['Letters only'] = new Validation.LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
    for (var name in validators) {
        console.log('"' + s + '" ' + (validators[name].isAcceptable(s) ? ' matches ' : ' does not match ') + name);
    }
});

컴파일할 때는 파일들을 컴파일 해서 하나의 JavaScript 파일(--out sample.js)로 만들거나
tsc --out sample.js Validation.ts LettersOnlyValidator.ts ZipCodeValidator.ts Test.ts
각각의 파일들을 컴파일 해서 모든 JavaScript 결과물들을 명시하는 방법이 있다.
    <script src="Validation.js" type="text/javascript" />
    <script src="LettersOnlyValidator.js" type="text/javascript" />
    <script src="ZipCodeValidator.js" type="text/javascript" />
    <script src="Test.js" type="text/javascript" />

[Going External]

위의 reference tag를 사용하지 않고 아래 import를 사용하는 방법이 있음.

import someMod = require('someModule');

컴파일 시 import 구문을 확인하여 관련 파일들을 컴파일하게 된다.
다만 컴파일 시 --module flag를 지정하여 external module target을 알려줘야 한다. (자세한 개념은 모르겠으니 아래 링크를 참고
- JavaScript 표준을 위한 움직임: CommonJS와 AMD [http://helloworld.naver.com/helloworld/12864] )

tsc --module commonjs Test.ts

Validation.ts
export interface StringValidator {
    isAcceptable(s: string): boolean;
}
LettersOnlyValidator.ts
import validation = require('./Validation');
var lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements validation.StringValidator {
    isAcceptable(s: string) {
        return lettersRegexp.test(s);
    }
}
ZipCodeValidator.ts
import validation = require('./Validation');
var numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements validation.StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
Test.ts
import validation = require('./Validation');
import zip = require('./ZipCodeValidator');
import letters = require('./LettersOnlyValidator');

// Some samples to try
var strings = ['Hello', '98052', '101'];
// Validators to use
var validators: { [s: string]: validation.StringValidator; } = {};
validators['ZIP code'] = new zip.ZipCodeValidator();
validators['Letters only'] = new letters.LettersOnlyValidator();
// Show whether each string passed each validator
strings.forEach(s => {
    for (var name in validators) {
        console.log('"' + s + '" ' + (validators[name].isAcceptable(s) ? ' matches ' : ' does not match ') + name);
    }
});

** 흠.. 모르겠다.. 일단.. 생략..

[Functions]

일반적인 TypeScript에서의 function definition이다.

var myAdd: (x:number, y:number)=>number = 
    function(x: number, y: number): number { return x+y; };

[Inferring the types]

첫번째에서는 function parameter의 type을 지정했지만 두번째 에서는 x, y의 type을 지정하지 않았지만 TypeScript compiler에서는 number로 유추하여 처리한다고 함.

// myAdd has the full function type
var myAdd = function(x: number, y: number): number { return x+y; };

// The parameters 'x' and 'y' have the type number
var myAdd: (baseValue:number, increment:number)=>number = 
    function(x, y) { return x+y; };

[Optional and Default Parameters]

JavaScript와 다르게 TypeScript에서는 function의 parameter의 수가 맞아야 한다.

function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName;
}

var result1 = buildName("Bob");  //error, too few parameters
var result2 = buildName("Bob", "Adams", "Sr.");  //error, too many parameters
var result3 = buildName("Bob", "Adams");  //ah, just right

Parameter를 optional로 처리하기 위해서는 다음과 같이 '?'를 붙여주면 됨.

function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}

var result1 = buildName("Bob");  //works correctly now
var result2 = buildName("Bob", "Adams", "Sr.");  //error, too many parameters
var result3 = buildName("Bob", "Adams");  //ah, just right

아니면 default value를 지정해 주어 처리할 수 도 있음.

unction buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}

var result1 = buildName("Bob");  //works correctly now, also
var result2 = buildName("Bob", "Adams", "Sr.");  //error, too many parameters
var result3 = buildName("Bob", "Adams");  //ah, just right

[Rest Parameters]

reset parameter 명칭이 맞는지 모르겠지만 몇개의 parameter가 들어올 지 모르는 함수의 경우 C 같은 곳에서 '...'로 지정해 놓는 것과 같은 문법을 TypeScript에서 지원한다.

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

var employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

JavaScript에서는 아래와 같이 처리할 수 있음.

https://www.inkling.com/read/javascript-definitive-guide-david-flanagan-6th/chapter-8/function-arguments-and
function max(/* ... */) {
    var max = Number.NEGATIVE_INFINITY;
    // Loop through the arguments, looking for, and remembering, the biggest.
    for(var i = 0; i < arguments.length; i++)
        if (arguments[i] > max) max = arguments[i];
    // Return the biggest
    return max; 
}

var largest = max(1, 10, 100, 2, 3, 1000, 4, 5, 10000, 6);  // => 10000
[Lambdas and using 'this']

JavaScript에서 this 사용은 강력하고 유연한 객체 접근을 도와주지만 호출 시 올바른 객체에 접근할 수 있어야 하므로 cost가 발생된다.
아래의 예제에서 cardPicker()를 호출할 경우 deck.createCardPicker()내의 this는 deck를 가리키는게 아니라 Window 개체를 가리키게 되어 정상적으로 동작되지 않는다.

var deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        return function() {
            var pickedCard = Math.floor(Math.random() * 52);
            var pickedSuit = Math.floor(pickedCard / 13);
   
            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

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

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

다른 방법으로 수정할 수 있으나 (아래 링크 참조) lambda 문법을 사용해서 생성되었을 때의 this를 capture할 수 있다.

Understanding JavaScript Function Invocation and “this”
: http://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/
JavaScript를 제대로 몰라서 위의 방법을 몰라 눈가리고 아웅식으로 전역처리해서 넘어갔었는데.. 부끄럽다..

var deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        // Notice: the line below is now a lambda, allowing us to capture 'this' earlier
        return () => {
            var pickedCard = Math.floor(Math.random() * 52);
            var pickedSuit = Math.floor(pickedCard / 13);
   
            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

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

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

[Overloads]

... 나중에..



[Generics]


Generic을 사용하는 이유 중 하나는 데이터형에 상관없이 유연한 재사용 가능한 코드를 만드는 것이겠고 그것을 Type script에서는 지원한다.

기본적으로 아래 코드르
function identity(arg: number): number {
    return arg;
}
모든 type을 받고 제공할 수 있도록 고칠 수 있고
function identity(arg: any): any {
    return arg;
}
좀 더 나아가서 함수에서 사용하는 data type을 아래와 같이 선언해 줄 수 있음.
function identity<T>(arg: T): T {
    return arg;
}
위 Generic 함수를 사용하는 방법은 아래와 같이 explicit, implicit 하게 쓸 수 있음.

var output = identity<string>("myString");  // type of output will be 'string'

var output = identity("myString");  // type of output will be 'string'
코드의 간결함을 위해 implicit하게 사용할 수 있으며 이때 compiler는 argument type을 기반하여 generic type을 유추한다.


[Working with Generic Type Variables]

T type의 array을 argument로 사용하려면 두번째 예제 처럼 해당 type을 T의 array type으로 특수화 해야 함.

function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  // Error: T doesn't have .length
    return arg;
}
function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

[Generic Types]

기존 function prototype ( argument :  type ) => return 앞에 generic type <T>만 붙여
Generic typed function을 type의 varaialbe을 선언하는 방법과

function identity<T>(arg: T): T {
    return arg;
}

var myIdentity: <T>(arg: T)=>T = identity;
object literal type으로 지정해서 사용할 수 있음.
function identity<T>(arg: T): T {
    return arg;
}

var myIdentity: {<T>(arg: T): T} = identity;
interface를 지정해서 사용하는 방법도 있다.
하지만 interface의 generic type이 명시적으로 선언되어 있지 않아 모호한 상황이 발생할 수도 있겠다.
interface GenericIdentityFn {
    <T>(arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

var myIdentity: GenericIdentityFn = identity;
interface도 generic type으로 선언해서 interface 사용 시 명시적으로 generic type을 지정하도록 하는게 바람직 할 듯.
interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

var myIdentity: GenericIdentityFn<number> = identity;

[Generic Classes]

문법은 일반적이다.
class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

var myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
var stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };

alert(stringNumeric.add(stringNumeric.zeroValue, "test"));
어떤 class type parameter를 선언해서 GenericNumber class를 사용해도 상관이 없으나 static member에는 class type parameter를 사용하지 못한다.

[Generic Constraints]

Generic type의 member method를 제한하고 싶을 경우 interface를 사용하여 제어할 수 있음.

interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}
아래와같이 interface를 맞춰 주면 ok
loggingIdentity({length: 10, value: 3});  

[Using Type parameters in Generic Constraints]

.. 나중에

function find<T, U extends Findable<T>>(n: T, s: U) {   // errors because type parameter used in constraint
  // ...
} 
find (giraffe, myAnimals);

function find<T>(n: T, s: Findable<T>) {   // errors because type parameter used in constraint
  // ...
} 
find(giraffe, myAnimals);


[Using Class Types in Generics]

.. 나중에

function create<T>(c: {new(): T; }): T { 
    return new c();
}


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 findKeeper<A extends Animal, K> (a: {new(): A; 
    prototype: {keeper: K}}): K {

    return null;
}

findKeeper(Lion).nametag;  // works!
















댓글 없음:

댓글 쓰기