ES 6

TypeScript

Typescript = JavaScript Superset

상위집합

Typescript = Transpiler

트랜스파일러

TypeScript 파일(ts)은 웹 브라우저에서 바로 해석될 수 없습니다.
브라우저에서 해석 가능한 언어인 JavaScript로 변경되어야
브라우저는 이를 인식하고 해석할 수 있습니다.

TypeScript를 JavaScript로 변환해야
웹 브라우저가 처리 가능
합니다.

TypeScript Playground

개발 환경 구성

NodeJS

NVM

nvm-windows

Yarn

~ ❯ cd Desktop
                                                                                                                                                                                                         
Desktop ❯ mkdir -p ts-dev/public

Desktop ❯ cd ts-dev

ts-dev ❯ yarn init

yarn init v1.7.0
question name (ts-dev):
question version (1.0.0):
question description: TypeScript 개발 환경 구성
question entry point (index.js):
question repository url:
question author: 야무<yamoo9@naver.com>
question license (MIT):
question private: true
success Saved package.json
✨  Done in 34.96s.

디렉토리 구성

Initialization

ts-dev ❯ yarn add typescript --dev

yarn add v1.7.0
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...

success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
└─ typescript@3.0.1
info All dependencies
└─ typescript@3.0.1
✨  Done in 0.70s.

TypeScript Package

ts-dev ❯ npx tsc ./public/js/main
ts-dev ❯ npx tsc --init
{
  "compilerOptions": {

    /* 기본 옵션
     * ------------------------------------------------------------------------------------------------------------------------------------------------ */
    "target": "es5",                          /* ECMAScript 목표 버전 설정: 'ES3'(기본), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
    "module": "commonjs",                     /* 생성될 모듈 코드 설정: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    // "lib": [],                             /* 컴파일 과정에 사용될 라이브러리 파일 설정 */
    // "allowJs": true,                       /* JavaScript 파일 컴파일 허용 */
    // "checkJs": true,                       /* .js 파일 오류 리포트 설정 */
    // "jsx": "preserve",                     /* 생성될 JSX 코드 설정: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* '.d.ts' 파일 생성 설정 */
    // "sourceMap": true,                     /* 소스맵 '.map' 파일 생성 설정 */
    // "outFile": "./",                       /* 복수 파일을 묶어 하나의 파일로 출력 설정 */
    // "outDir": "./dist",                    /* 출력될 디렉토리 설정 */
    // "rootDir": "./",                       /* 입력 파일들의 루트 디렉토리 설정. --outDir 옵션을 사용해 출력 디렉토리 설정이 가능 */
    // "removeComments": true,                /* 출력 시, 주석 제거 설정 */
    // "noEmit": true,                        /* 출력 방출(emit) 유무 설정 */
    // "importHelpers": true,                 /* 'tslib'로부터 헬퍼를 호출할지 설정 */
    // "downlevelIteration": true,            /* 'ES5' 혹은 'ES3' 타겟 설정 시 Iterables 'for-of', 'spread', 'destructuring' 완벽 지원 설정 */
    // "isolatedModules": true,               /* 각 파일을 별도 모듈로 변환 ('ts.transpileModule'과 유사) */

    /* 엄격한 유형 검사 옵션
     * ------------------------------------------------------------------------------------------------------------------------------------------------ */
    "strict": true,                           /* 모든 엄격한 유형 검사 옵션 활성화 */
    // "noImplicitAny": true,                 /* 명시적이지 않은 'any' 유형으로 표현식 및 선언 사용 시 오류 발생 */
    // "strictNullChecks": true,              /* 엄격한 null 검사 사용 */
    // "strictFunctionTypes": true,           /* 엄격한 함수 유형 검사 사용 */
    // "strictPropertyInitialization": true,  /* 클래스에서 속성 초기화 엄격 검사 사용 */
    // "noImplicitThis": true,                /* 명시적이지 않은 'any'유형으로 'this' 표현식 사용 시 오류 발생 */
    // "alwaysStrict": true,                  /* 엄격모드에서 구문 분석 후, 각 소스 파일에 "use strict" 코드를 출력 */

    /* 추가 검사 옵션
     * ------------------------------------------------------------------------------------------------------------------------------------------------ */
    // "noUnusedLocals": true,                /* 사용되지 않은 로컬이 있을 경우, 오류로 보고 */
    // "noUnusedParameters": true,            /* 사용되지 않은 매개변수가 있을 경우, 오류로 보고 */
    // "noImplicitReturns": true,             /* 함수가 값을 반환하지 않을 경우, 오류로 보고 */
    // "noFallthroughCasesInSwitch": true,    /* switch 문 오류 유형에 대한 오류 보고 */

    /* 모듈 분석 옵션
     * ------------------------------------------------------------------------------------------------------------------------------------------------ */
    // "moduleResolution": "node",            /* 모듈 분석 방법 설정: 'node' (Node.js) 또는 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./",                       /* 절대 경로 모듈이 아닌, 모듈이 기본적으로 위치한 디렉토리 설정 (예: './modules-name') */
    // "paths": {},                           /* 'baseUrl'을 기준으로 상대 위치로 가져오기를 다시 매핑하는 항목 설정 */
    // "rootDirs": [],                        /* 런타임 시 프로젝트 구조를 나타내는 로트 디렉토리 목록 */
    // "typeRoots": [],                       /* 유형 정의를 포함할 디렉토리 목록 */
    // "types": [],                           /* 컴파일 시 포함될 유형 선언 파일 입력 */
    // "allowSyntheticDefaultImports": true,  /* 기본 출력(default export)이 없는 모듈로부터 기본 호출을 허용 (이 코드는 단지 유형 검사만 수행) */
    "esModuleInterop": true                   /* 모든 가져오기에 대한 네임스페이스 객체 생성을 통해 CommonJS와 ES 모듈 간의 상호 운용성을 제공. 'allowSyntheticDefaultImports' 암시 */
    // "preserveSymlinks": true,              /* symlinks 실제 경로로 결정하지 않음 */

    /* 소스맵 옵션
     * ------------------------------------------------------------------------------------------------------------------------------------------------ */
    // "sourceRoot": "./",                    /* 디버거(debugger)가 소스 위치 대신 TypeScript 파일을 찾을 위치 설정 */
    // "mapRoot": "./",                       /* 디버거가 생성된 위치 대신 맵 파일을 찾을 위치 설정 */
    // "inlineSourceMap": true,               /* 하나의 인라인 소스맵을 내보내도록 설정 */
    // "inlineSources": true,                 /* 하나의 파일 안에 소스와 소스 코드를 함께 내보내도록 설정. '--inlineSourceMap' 또는 '--sourceMap' 설정이 필요 */

    /* 실험적인 기능 옵션
     * ------------------------------------------------------------------------------------------------------------------------------------------------ */
    // "experimentalDecorators": true,        /* ES7 데코레이터(decorators) 실험 기능 지원 설정 */
    // "emitDecoratorMetadata": true,         /* 데코레이터를 위한 유형 메타데이터 방출 실험 기능 지원 설정 */

  }
}

타입

let milk_chocolate = '밀크 초콜릿';
milk_chocolate = 2018;

/*
  오류 출력:
    [ts] '2018' 형식은 'string' 형식에 할당할 수 없습니다.
    let milk_chocolate: string
*/

Type inference 

타입 추론

let coffee_type; // `any` 타입

coffee_type = '콜드브루';
coffee_type = 9112304129312;

Don't Type inference 

타입 추론을 하지 않는 경우

let coffee_type:string;

coffee_type = '콜드브루';

coffee_type = 9112304129312;
/*
  [ts] '9120304123' 형식은 'string' 형식에 할당할 수 없습니다.
  let coffee_type: string
*/

Explicit Type Declaration 

명시적 타입 선언

// 명시적으로 number 타입을 설정
let product_id:number = 124981;

// 명시적으로 string 타입을 설정
let product_name:string = 'VR 글래스';

// 명시적으로 boolean 타입을 설정
let is_waterprofing:boolean = false;


// -------------------------------------------------------


// [오류]
// [ts] '"p9023412"' 형식은 'number' 형식에 할당할 수 없습니다.
// let product_id: number
product_id = 'p9023412';

// [오류]
// [ts] '() => void' 형식은 'string' 형식에 할당할 수 없습니다.
// let product_name: string
product_name = function(){};

// [오류]
// [ts] '() => void' 형식은 'string' 형식에 할당할 수 없습니다.
// let product_name: string
is_waterprofing = 0;

Primitive Types 

프리미티브 타입

Practice

// 명시적으로 any 타입 지정
let product_id:any = 124981;

// any 유형이 설정되었으므로 어떤 유형도 값으로 할당 가능
product_id = 'p9023412';


// -------------------------------------------


// 암시적으로 any 타입 지정
let product_id;

product_id = 124981;
product_id = 'p9023412';

Any Types 

애니 타입

Practice

let members = ['이권', '감장겸', '장도일']; // 암시적 타입 선언(타입 추론)

// [오류]
// [ts] 'number[]' 형식은 'string[]' 형식에 할당할 수 없습니다.
//      'number' 형식은 'string' 형식에 할당할 수 없습니다.
// let members: string[]
members = [9, 13, 26];

Array Type 

배열 타입

let members:string[] = ['이권', '감장겸', '장도일']; // 명시적 타입 선언
// 오직 숫자 아이템만 허용
let nums:number[] = [100, 101, 102];

// 오직 문자 아이템만 허용
let strs:string[] = ['ㄱ', 'ㄴ', 'ㄷ'];

// 오직 불리언 아이템만 허용
let boos:boolean[] = [true, false, true];

// 모든 데이터 타입을 아이템으로 허용
let anys:any[] = [100, 'ㄴ', true];

// 특정 데이터 타입만 아이템으로 허용
let selects:(number|string)[] = [102, 'ㅇ'];

Practice

Tuple Type 

터플 타입

let book__name_price:[string, number] = ['카밍 시그널', 13320];

// [오류]
// [ts] '[number, string]' 형식은 '[string, number]' 형식에 할당할 수 없습니다.
//      'number' 형식은 'string' 형식에 할당할 수 없습니다.
// let book__name_price: [string, number]
book__name_price = [13320, '카밍 시그널'];

// [오류]
// [ts] 'false' 형식의 인수는 'string | number' 형식의 매개 변수에 할당될 수 없습니다.
book__name_price.push(false);

Practice

Enum Type 

열거형 타입

enum Team {
  Manager,   // 0
  Planner,   // 1
  Developer, // 2
  Designer,  // 3
}

let sarha:number = Team.Designer; // (enum member) Team.Designer = 3
enum Team {
  Manager   = 101,
  Planner   = 208,
  Developer = 302,
  Designer, // 302 + 1
}

let yamoo9:number = Team.Manager; // (enum member) Team.Manager = 101
let sarha:number = Team.Designer; // (enum member) Team.Designer = 303

Practice

Parameters Type 

매개변수 타입

// tsconfig.json ⟹ noImplicitAny: true 일 경우
// ———————————————————————————————————————————————————
// [오류]
// [ts] 'id' 매개 변수에는 암시적으로 'any' 형식이 포함됩니다.
// (parameter) id: any
// [ts] 'name' 매개 변수에는 암시적으로 'any' 형식이 포함됩니다.
// (parameter) name: any
function setInfo(id, name) {
  return { id, name };
}

let product_one = setInfo(120912, '스노우보드');
// 명시적 매개변수 타입 지정
function setInfo(id:number, name:string) {
  return { id, name };
}
​
let product_one = setInfo(120912, '스노우보드');

Practice

Union Type 

유니온 타입

// 유니온(합병) 타입은 파이프(|) 기호를 사용해 타입을 지정
function setInfo(id:number|string, name:string) {
  return { id, name };
}


let product_one = setInfo(120912, '스노우보드');

let product_two = setInfo('P-014921', 'VR 헤드셋');

Practice

Return Type 

리턴 타입

// 리턴 값 타입이 명시적으로 설정되지 않는 함수
function assignClass(name:string): void {
  document.documentElement.classList.add(name);
}

// 리턴 값 타입이 숫자인 함수
function factorial(n:number): number {
  if (n < 0) { return 0; }
  if (n === 1) { return 1; }
  return n * factorial(n-1);
}

// 리턴 값 타입이 문자인 경우
function repeat(text:string, count:number = 1): string {
  let result:string = '';
  while(count--) { result += text; }
  return result;
}

Practice

Function Expression Type 

함수 식 타입

// 변수에 함수 값을 할당하는 식(Expression)은 컴파일 과정에서 오류를 발생시키지 않습니다.
let assignClass = function(name) {
  document.documentElement.classList.add(name);
};
// 변수에 함수 매개변수, 리턴 타입에 대한 명시적 설정
let assignClass: (n:string) => void;

// 변수에 함수 값 할당
assignClass = function(name) {
  document.documentElement.classList.add(name);
};
// 변수에 명시적 타입 설정과 함수 값 할당 구문을 별도로 나누지 않고, 한번에 정의할 수도 있습니다.
let factorial:(n:number)=>number = function (n) {
  if (n < 0) { return 0; }
  if (n === 1) { return 1; }
  return n * factorial(n-1);
};
let factorial:(n:number)=>number = n => n < 0 ? 0 : n === 1 ? 1 : n * factorial(n-1);

Practice

Object Type 

객체 타입

let Dom = {
  version: '0.0.1',
  el(){},
  css(){}
};

// [오류]
// [ts]
//   '{ append(): void; }' 형식은 '{ version: string; el(): void; css(): void; }' 형식에 할당할 수 없습니다.
//   객체 리터럴은 알려진 속성만 지정할 수 있으며 '{ version: string; el(): void; css(): void; }' 
//   형식에 'append'이(가) 없습니다. (method) append(): void
Dom = {
  append(){}
};


// -------------------------------------------------------------------------------------------------


let Dom: {version:string, el:()=>void, css:()=>void};

Dom = {
  version: '0.0.1',
  el(){},
  css(){}
};

Object Type 

객체 타입

// 타입으로 설정되지 않은 객체의 속성을 새롭게 추가할 경우, 다음과 같은 오류 메시지를 출력합니다.
// ——————————————————————————————————————————————————————————————————————————————————————
// [오류]
// [ts] '{ version: string; el: () => void; css: () => void; }' 형식에 'each' 속성이 없습니다.
// any
Dom.each = function(){};


// --------------------------------------------------------------------------------------


let Dom: {
  version: string,
  el: () => void,
  css: () => void,
  [propName: string]: any // ⬅︎
};
​
Dom = {
  version: '0.0.1',
  el(){},
  css(){}
};
​
Dom.each   = function(){};
Dom.map    = function(){};
Dom.filter = function(){};

Practice

nothing Type 

낫싱 타입

// null, undefined는 데이터 타입 이자 하나의 값입니다. 
// 하나의 타입으로 다음과 같이 타입을 지정할 수 있습니다.
let nullable:null = null;
let undefinedable:undefined = undefined;


// 하지만 지정된 타입이 아닌, 값이 할당되면 오류를 출력합니다.
// [오류]
// [ts] 'undefined' 형식은 'null' 형식에 할당할 수 없습니다.
// let nullable: null
nullable = undefined;



// 유니온 타입을 사용해 복수의 타입을 지정해 문제를 해결할 수 있습니다.
let assign_name:string|null = null;
​
if (!assign_name) {
  assign_name = '미네랄';
}

strictNullChecks 

엄격한 null 체크 설정

// tsconfig.json
"strictNullChecks": false
// 오류가 출력되지 않습니다.
let assign_name:string = null;
​
if (!assign_name) {
  assign_name = '미네랄';
}

Never Type 

네버 타입

// 항상 오류 발생
function invalid(message:string): never {
  throw new Error(message);
}

// never 타입을 결과 추론(Inferred)
function fail() {
  return invalid('실패');
}

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

// ------------------------------------------------------------

let never_type:never;

// 오류 발생: 숫자 값을 never 타입 변수에 할당할 수 없습니다.
never_type = 99;

// 함수의 반환 값이 never 타입 이기 때문에 오류가 발생하지 않습니다.
never_type = (function():never { throw new Error('ERROR') })();

Practice

Custom Type 

사용자 정의 타입

let sum:{ data: number[], output:(num:number)=>number[] } = {
  data: [10, 30, 60],
  output(num){
    return this.data.map(n=>n+num);
  }
};
​
let multiply:{ data: number[], output:(num:number)=>number[] } = {
  data: [110, 230, 870, 231],
  output(num){
    return this.data.map(n=>n*num);
  }
};


// ------------------------------------------------------------------------------


// 사용자 정의 타입 operation 정의
// 타입 별칭(Type Alias)
type operation = {
  data: number[],
  output:(num:number)=>number[]
};

Practice

Type Assertion 

타입 단언

// 타입 어설션(단언)

let assertion:any = "타입 어설션은 '타입을 단언'합니다.";


// 방법 1: 앵글 브라켓(<>) 문법을 사용하여 assertion 변수의 타입을 string으로 단언 처리
let assertion_count:number = (<string>assertion).length;


// 방법 2: as 문법을 사용하여 assertion 변수의 타입을 string으로 단언 처리
let assertion_count:number = (assertion as string).length;

Practice

클래스

ES6 Class 

JavaScript 클래스

/* 클래스 정의 ------------------------------------------------ */
class Book {

  /* 생성자 */
  constructor(title, author, pages) {
    this.title  = title;
    this.author = author;
    this.pages  = pages;
    this.init();
  }

  /* 클래스 메서드 */
  static create(){}

  /* 인스턴스 메서드 */
  init(){}

}

/* 인스턴스 생성 ------------------------------------------------ */

let indRevo = new Book('한 권으로 정리하는 4차 산업혁명', '최진기', 367);
console.log(indRevo); // Book {}

Property Access Modifiers 

TypeScript 클래스 ⟹ 속성 접근 제어자

class Book {

  // 제목
  // public: 클래스 외부에서 접근 가능
  public title:string;

  // 저자
  // public은 기본 값으로 생략 가능합니다.
  author:string;

  // 제조 공장
  // private: Book 클래스 내부에서만 접근 가능
  private _manufacturing_plant:string;

  // 종이 유형
  // protected: Book 클래스를 포함한 서브 클래스에서만 접근 가능
  protected paper_type:string;

  // constructor() 매개변수 앞에
  // public, private, protected를 사용하면
  // Book 클래스의 타입(type)을 별도 선언하지 않아도 됩니다.
  constructor(title:string, author:string, public pages:number) {
    this.title = title;
    this.author = author;
    this.pages = pages;
  }

}

/* 인스턴스 생성 ------------------------------------------------ */

let indRevo = new Book('한 권으로 정리하는 4차 산업혁명', '최진기', 367);
console.log(indRevo); // Book {}

Practice

Method Access Modifiers 

TypeScript 클래스 ⟹ 메서드 접근 제어자

class Book {

  public    title:string;
  public    author:string;
  public    pages:number = 150;
  private   _manufacturing_plant:string = '충무로 공장';
  protected paper_type:string = '밍크지';

  constructor(title:string, author:string, pages:number) {
    this.title  = title;
    this.author = author;
    this.pages  = pages;
  }

  /* 메서드 ------------------------------------------------ */

  // public 메서드
  // 클래스 외부에서 접근 가능
  public printPages(): string {
    return `${this.pages}페이지`;
  }

  // protected 메서드
  // Book 클래스를 포함한 서브 클래스에서만 접근 가능
  protected changePaperType(type:string): void {
    this.paper_type = type;
  }

  // private 메서드
  // Book 클래스 내부에서만 접근 가능
  private setManufacturingPlant(plant:string): void {
    this._manufacturing_plant = plant;
  }


  /* 클래스 내부 메서드에서 private, protected 메서드 접근 가능 */

  public setPaperType(type:string):void {
    // protected 메서드 접근 가능
    this.changePaperType(type);
    console.log(this.paper_type);
  }

  public setPlant(plant:string):void {
    // private 메서드 접근 가능
    this.setManufacturingPlant(plant);
    console.log(this._manufacturing_plant);
  }

}


/* 인스턴스 생성 ------------------------------------------------ */

let indRevo = new Book('한 권으로 정리하는 4차 산업혁명', '최진기', 367);

console.log(indRevo.printPages()); // '367페이지'

// [오류]
// [ts] 'changePaperType' 속성은 보호된 속성이며
// 'Book' 클래스 및 해당 하위 클래스 내에서만 액세스할 수 있습니다.
// (method) Book.changePaperType(type: string): void
console.log(indRevo.changePaperType('인디언지'));

// [오류]
// [ts] 'setManufacturingPlant' 속성은 private이며
// 'Book' 클래스 내에서만 액세스할 수 있습니다.
// (method) Book.setManufacturingPlant(plant: string): void
console.log(indRevo.setManufacturingPlant('파주 공장'));

Practice

Class Inheritance 

클래스 상속

// Book 수퍼 클래스를 상속 받은 E_Book 클래스
class E_Book extends Book {
  // paper_type 오버라이딩
  paper_type = '스크린';
}


// --------------------------------------------------------


// [오류]
// [ts]
// 'E_Book' 클래스가 기본 클래스 'Book'을(를) 잘못 확장합니다.
//   'manufacturing_plant' 속성은 'Book' 형식에서 private 이지만
//   'E_Book' 형식에서는 그렇지 않습니다.
// class E_Book
class E_Book extends Book {
  paper_type = '스크린';

  // [오류]
  _manufacturing_plant = '출판사 웹서버';

}

Constructor / Super 

클래스 상속 ⟹ 생성자 / 수퍼

class E_Book extends Book {

  paper_type = '스크린';

  constructor(
    title: string, 
    author: string, 
    pages: number, 
    public is_downloadable: boolean
  ) {

    // 수퍼 클래스 constructor를 덮어쓰기 위해서는 super() 실행이 필요합니다.
    super(title, author, pages);

    this.is_downloadable = is_downloadable;

  }

}

Constructor / Super 

클래스 상속 ⟹ 생성자 / 수퍼

class E_Book extends Book {

  constructor(
    title:string, 
    author:string, 
    pages:number, 
    public is_downloadable:boolean
  ) {

    super(title, author, pages);
    this.is_downloadable = is_downloadable;

    // 수퍼 클래스의 protected 속성은 접근 가능
    console.log(this.paper_type);

    // 수퍼 클래스의 private 속성은 접근 불가능
    // [오류]
    // [ts] '_manufacturing_plant' 속성은 private이며 'Book' 클래스 내에서만 액세스할 수 있습니다.
    // (property) Book._manufacturing_plant: string
    console.log(this._manufacturing_plant);

  }

}

Practice

Getters / Setters 

게터 / 세터

class Plant {

  // 비공개 속성 '종(Species)'
  private _species:string|null = null;

  // getter 함수
  get species(): string {
    return this._species;
  }

  // setter 함수
  set species(value:string) {
    if ( value.length > 3 ) { this._species = value; }
  }

}


/* 인스턴스 생성 ------------------------------------------------ */

let plant = new Plant();

console.log(plant.species); // null

plant.species = '줄기';

console.log(plant.species); // null

plant.species = '푸른 식물';

console.log(plant.species); // '푸른 식물'

Practice

Static Properties 

클래스 속성 / 메서드

class Mathmatics {

  // 스태틱 속성
  static PI:number = Math.PI;

  // 스태틱 메서드
  // circumference = 둘레(원주)
  static calcCircumference(radius:number) :number {
    return this.PI * radius * 2;
  }

  static calcCircleWidth(radius:number): number {
    return this.PI * Math.pow(radius, 2);
  }

}

// radius = 반지름
let radius = 4;

console.log('PI(원주율) = ', Mathmatics.PI);
console.log(`반지름이 ${radius}인 원의 넓이: πr² = `, Mathmatics.calcCircleWidth(radius));
console.log(`반지름이 ${radius}인 원의 둘레: 2πr = `, Mathmatics.calcCircumference(radius));

Practice

Abstract Class 

추상 클래스

// 추상 클래스
abstract class Project {

  public project_name:string|null = null;
  private budget:number = 2000000000; // 예산

  // 추상 메서드 정의
  public abstract changeProjectName(name:string): void;

  // 실제 메서드 정의
  public calcBudget(): number {
    return this.budget * 2;
  }

}

// [오류]
// [ts] 추상 클래스의 인스턴스를 만들 수 없습니다.
// constructor Project(): Project
let new_project = new Project();

Abstract Class 

추상 클래스

// 클래스 ⟸ 추상 클래스 상속
class WebProject extends Project {
  // [오류]
  // [ts] 비추상 클래스 'WebProject'은(는) 'Project' 클래스에서 상속된
  // 추상 멤버 'changeProjectName'을(를) 구현하지 않습니다.
  // class WebProject
}

// -----------------------------------------------------------------

class WebProject extends Project {

  // 추상 클래스에 정의된 추상 메서드 구현
  changeProjectName(name:string): void {
    this.project_name = name;
  }

}


/* 인스턴스 생성 ------------------------------------------------ */

let new_project = new WebProject();

console.log(new_project.project_name); // null

new_project.changeProjectName('CJ 올리브 네트웍스 웹사이트 개편');

console.log(new_project.project_name); // 'CJ 올리브 네트웍스 웹사이트 개편'

Practice

Singleton Pattern 

싱글턴 패턴

class OnlyOne {

  private static instance: OnlyOne;

  public name:string;

  // new 클래스 구문 사용 제한을 목적으로
  // constructor() 함수 앞에 private 접근 제어자 추가
  private constructor(name:string) {
    this.name = name;
  }
  
  // 오직 getInstance() 스태틱 메서드를 통해서만
  // 단 하나의 객체를 생성할 수 있습니다.
  public static getInstance() {
    if (!OnlyOne.instance) {
      OnlyOne.instance = new OnlyOne('싱글턴 객체');
    }
    return OnlyOne.instance;
  }
  
}


/* 인스턴스 생성 ------------------------------------------------ */

// [오류]
// [ts] 'OnlyOne' 클래스의 생성자는 private이며 클래스 선언 내에서만 액세스할 수 있습니다.
// constructor OnlyOne(name: string): OnlyOne
let bad_case = new OnlyOne('오류 발생');

let good_case = OnlyOne.getInstance();

Practice

Readonly 

읽기 전용 속성

class OnlyOne {

  private static instance:OnlyOne;

  // 읽기 전용 속성 설정
  public readonly name:string;

  private constructor(name:string) {
    this.name = name;
  }

  public static getInstance(name:string):OnlyOne {
    if (!OnlyOne.instance) {
      OnlyOne.instance = new OnlyOne(name);
    }
    return OnlyOne.instance;
  }

}


/* 인스턴스 생성 ------------------------------------------------ */

let special_one = OnlyOne.getInstance('스페셜 원');

console.log(special_one.name);

// [오류]
// [ts] 상수 또는 읽기 전용 속성이므로 'name'에 할당할 수 없습니다.
// (property) OnlyOne.name: string
special_one.name = '노멀 원';

Practice

인터페이스

Declaration Interface 

인터페이스 정의

// 인터페이스 Button 정의
interface ButtonInterface {
  onInit():void;
  onClick():void;
}


// -------------------------


// 커스텀 타입과 유사해 보임
type ButtonType = {
  onInit():void;
  onClick():void;
}

Interface vs Custom Type 

인터페이스 vs 커스텀 타입

/// 인터페이스는 병합 가능
interface ButtonInterface {
  onInit():void;
  onClick():void;
}

...

interface ButtonInterface {
  onToggle():void;
}


// ----------------------------------


/// 커스텀 타입은 병합 불가능
type ButtonType = {
  onInit():void;
  onClick():void;
}

// [오류]
// 'ButtonType' 식별자가 중복되었습니다.
type ButtonType = {
  onToggle():void;
}

Practice

Interface ↦ Class 

인터페이스 : 클래스 이행 규칙

// 인터페이스 Button 정의
interface ButtonInterface {
  onInit():void;
  onClick():void;
}


// 클래스 Y9Button 인터페이스 Button 확장
class Y9Button implements ButtonInterface {

  width:number;
  height:number;
  
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }
  
  // [오류]
  // 'Y9Button' 클래스가 'Button' 인터페이스를 잘못 구현합니다.
  // 'onInit' 속성이 'Y9Button' 형식에 없습니다.

}

Practice

// onInit(), initialize() 메서드가 필요함을 정의한 인터페이스
interface OnInitInterface {
  onInit():void;
  initialize():void;
}
​
// 인터페이스 요구 조건에 충족하는 객체
const o = {
  onInit():void { console.log('onInit 라이프 사이클') },
  initialize():void { console.log('객체 초기화') }  
};

// 인터페이스 요구 조건에 충족하지 않은 객체​
const j = {
  settings():void { console.log('객체 설정') }
};
​
// 매개변수에 인터페이스가 설정된 함수
function ready(m:OnInitInterface):void {
  m.onInit();
  m.initialize();
}
​
// 전달된 객체 o는 OnInitInterface 인터페이스 요구 조건을 충족
ready(o);

// [오류]
// '{ settings(): void; }' 형식의 인수는 'OnInitInterface' 형식의 매개 변수에 할당될 수 없습니다.
// 'onInit' 속성이 '{ settings(): void; }' 형식에 없습니다.
ready(j);

Interface ↦ Paramters 

인터페이스 : 매개변수 이행 규칙

Practice

interface OnInitInterface {
  onInit():void;
  initialize():void;
}
​
const o:OnInitInterface = {
  onInit():void { console.log('onInit 라이프 사이클') },
  initialize():void { console.log('객체 초기화') }  
};
​
// [오류]
// '{ settings(): void; }' 형식은 'OnInitInterface' 형식에 할당할 수 없습니다.
// 개체 리터럴은 알려진 속성만 지정할 수 있으며 'OnInitInterface' 형식에 'settings'이(가) 없습니다.
const j:OnInitInterface = {
  settings():void { console.log('객체 설정') }
};

Interface ↦ Object Literal 

인터페이스 : 객체 리터럴 이행 규칙

Practice

Interface Options 

인터페이스 옵션 설정

interface ButtonInterface {
  // 속성 이름 뒤에 ? 기호가 붙으면 옵션 속성이 됩니다.
  onInit?():void;
  onClick():void;
}

class ButtonComponent implements ButtonInterface {

  // onInit 메서드가 설정되지 않아도 오류를 발생하지 않습니다.
  onClick() { console.log('버튼 클릭') }

}

Practice

Interface Readonly 

인터페이스 읽기전용 설정

interface Notebook { 
  readonly CPU: string; 
  readonly RAM: string;
};



let mackbook:Notebook = { 
  CPU: '2.9GHz 코어 i9',
  RAM: '32GB'
};

// [오류]
// 'RAM'은 읽기 전용 속성 또는 상수로 변경할 수 없습니다.
// (property) Notebook["RAM"]: string
macbook.RAM = '128GB';

Practice

Interface Signature 

인터페이스 × 클래스

interface ButtonInterface {
  onInit?():void;
  onClick():void;
}

class ButtonComponent implements ButtonInterface {
​
  // 클래스의 경우, 인터페이스에 명시 되지 않은 
  // 속성을 추가해도 오류가 발생하지 않습니다.
  type:string = 'button';
  disabled:boolean = false;

  constructor() {}
​
  onClick() { console.log('버튼 클릭') }

}

Interface Signature 

인터페이스 × 객체

interface ButtonInterface {
  onInit?():void;
  onClick():void;
}

const button:ButtonInterface = {

  // [오류]
  // '{ type: string; disabled: boolean; onClick(): void; }' 형식은 
  // 'ButtonInterface' 형식에 할당할 수 없습니다. 개체 리터럴은 알려진 속성만 
  // 지정할 수 있으며 'ButtonInterface' 형식에 'type'이(가) 없습니다.
  type: 'button',
  disabled: false,
  onClick() { console.log('버튼 클릭') }
  
};

Interface Signature 

인터페이스 시그니처

interface ButtonInterface {
  onInit?():void;
  onClick():void;

  // 인덱스 시그니처
  [prop:string]: any;

}

const button:ButtonInterface = {

  // 인터페이스에 설정되지 않은 속성이 추가되어도
  // 오류가 발생하지 않습니다.
  type: 'button',
  disabled: false,

  onClick() { console.log('버튼 클릭') }
};

Practice

Interface ↦ Function Type 

인터페이스 × 함수 타입

// 인터페이스를 연결하지 않은 함수의 경우, 매개변수, 리턴 값을 설정합니다.
const factorial = (n:number): number => {
  if (n === 0) { return 0; }
  if (n === 1) { return 1; }
  return n * factorial(n - 1);
}

// 펙토리얼 함수 인터페이스 정의
interface FactorialInterface {
  (n: number): number;  
}
​
// 인터페이스를 함수 타입에 설정했기에 별도의 매개변수, 리턴 값 설정을 생략해도 됩니다.
const facto: FactorialInterface = (n) => {
  if (n === 0) { return 0; }
  if (n === 1) { return 1; }
  return n * facto(n - 1);
};

Interface ↦ Function Type 

인터페이스 × 함수 타입

// 주의할 점은 인터페이스가 설정된 함수의 매개변수, 리턴 값 타입을 임의로 변경하면 오류가 발생합니다.


// [오류]
// '(n: number[]) => number' 형식은 'FactorialInterface' 형식에 할당할 수 없습니다.
// 'n' 및 'number' 매개 변수의 형식이 호환되지 않습니다.
// 'number' 형식은 'number[]' 형식에 할당할 수 없습니다.
const fct: FactorialInterface = (n:number[]): number => {
  if (n[0] === 0) { return 0; }
  if (n[0] === 1) { return 1; }
  return n[0] * facto(n[0] - 1);
};

Practice

Extends Interface 

인터페이스 확장

interface ButtonInterface {
  readonly _type:string;
  width?:number;
  height?:number;
  onInit?():void;
  onClick():void;
}

// ButtonInterface를 확장하는 ToggleButtonInterface
interface ToggleButtonInterface extends ButtonInterface {
  toggle():void;
  onToggled?():void;
}

// ButtonInterface를 확장하는 CounterButtonInterface
interface CounterButtonInterface extends ButtonInterface {
  increase():void;
  decrease():void;
  onIncreased?():void;
  onDecreased?():void;
}

Practice

Multiple Extends Interface 

인터페이스 다중 확장

interface ButtonInterface {
  readonly _type:string;
  width?:number;
  height?:number;
  onInit?():void;
  onClick():void;
}
​
interface ButtonSizeInterface {
  readonly _size:number;
  small():void;
  medium():void;
  large():void;
  onChangeSize?():void;
}
​
// ButtonInterface, ButtonSizeInterface를 다중 확장하는 ImageButtonInterface
interface ImageButtonInterface extends ButtonInterface, ButtonSizeInterface {
  readonly _url:string;
  getUrl():string;
  setUrl?(url:string):void;
  onChangeUrl?():void;
}

Practice

Class Extended by Interface 

인터페이스 ↦ 클래스 확장

// 인터페이스를 콤마(,)로 구분하여 다중 확장할 수 있습니다.
class ImageButton implements ImageButtonInterface {
  readonly _type:string;
  readonly _url:string;
  readonly _size:number;
  onClick(){}
  getUrl() { return this._url; }
  small() {}
  medium() {}
  large() {}
}

Practice

Object × Interface 

인터페이스 ↦ 객체

// [오류]
// '{}' 형식은 'ImageButtonInterface' 형식에 할당할 수 없습니다.
// '_url' 속성이 '{}' 형식에 없습니다.
let imageButton:ImageButtonInterface = {};


// --------------------------------------------------------


// 오류를 해결하려면 인터페이스가 요구하는 이행 조건을 모두 맞춰야 합니다.
let imageButton:ImageButtonInterface = {
  _url: '',
  _size: 14,
  _type: 'button',
  getUrl() { return this._url; },
  setUrl(url:string) { },
  small() { },
  medium() { },
  large() { },
  onClick() { },
};

Object × Interface 

인터페이스 ↦ 객체

// 제네릭(Generic) 문법을 사용하여 설정하면 
// 선언 과정에서 오류가 발생하지 않습니다.
let imageButton = <ImageButtonInterface>{};

imageButton.small = () => { console.log('버튼 크기 small 설정') };
imageButton.large = () => { console.log('버튼 크기 large 설정') };


// --------------------------------------------------------


// 하지만 readonly 속성의 경우는 
// 선언과정에서 초기 값이 설정되어야 합니다.
let imageButton = <ImageButtonInterface>{
  _type: 'button',
  _size: 14,
  _url: '',
};

imageButton.small = () => { console.log('버튼 크기 small 설정') };
imageButton.large = () => { console.log('버튼 크기 large 설정') };

Practice

제네릭

Model Class 

어떤 타입도 아이템으로 받을 수 있는 모델 클래스

class Model {
  
  private _data: any[] = [];
  
  constructor(data:any[]) {
    this._data = data;
  }
  
  get data():any { 
    return this._data; 
  }
  
  add(item:any):void { 
    this._data.push(item); 
  }
  
  remove(index:number):void { 
    this._data.splice(index, 1); 
  }
  
  item(index:number):any { 
    return this._data[index]; 
  }
  
  clear():void { 
    this._data = []; 
  }
  
}

ObjectModel Class 

모델 클래스를 상속 받은 오브젝트모델 클래스

// 특정 데이터 타입을 규정한 Model 클래스를 사용하고자 할 수도 있을 겁니다. 
// 이런 경우 클래스 상속을 이용한 새로운 클래스를 생성해 문제를 해결할 수 있습니다. 
// 예를 들어 객체 타입만 데이터로 추가할 수 있도록 하기 위한 ObjectModel 클래스를 
// 정의한다면 다음과 같이 구성합니다.
class ObjectModel extends Model {
​
  constructor(data:object[]=[]) { 
    super(data); 
  }
  
  add(item:object):void { 
    super.add(item); 
  }
​
}



// 하지만 클래스 상속을 사용하면 별도의 자료 타입을 받고자 하는 클래스를 
// 추가해야 하고 중복되는 코드를 양산하기에 불편합니다. 바로 이런 경우에 
// 유용하게 사용할 수 있는 것이 `제네릭` 입니다.

Practice

Generic ⇾ Class 

제레릭이 설정된 클래스

class Model<T> {
  
  private _data:T[] = [];
  
  constructor(data:T[]=[]) {
    this._data = data;
  }
  
  get data():T[] { 
    return this._data; 
  }
  
  add(item:T):void { 
    this._data.push(item); 
  }
  
  remove(index:number):void { 
    this._data.splice(index, 1); 
  }
  
  item(index:number):T { 
    return this._data[index]; 
  }
  
  clear():void { 
    this._data = []; 
  }
  
}

Generic ⇾ Class | Type Assertion 

제레릭이 설정된 클래스 | 타입 단언

const stringModel = new Model<string>();
​
stringModel.add('흔들의자');
​
// [오류]
// '2018' 형식의 인수는 'string' 형식의 매개 변수에 할당될 수 없습니다.
stringModel.add(2018);



// --------------------------------------------------------


// TypeScript 프로그래밍 과정에서 부득이하게 정해진 타입이 아닌 경우를 
// 사용해야 하는 경우가 종종 발생합니다. 이런 경우 타입 어설션 문법을 사용해 
// 컴파일 과정의 타입 검사를 우회할 수 있습니다만, 꼭 필요한 경우에만 
// 사용하는 것이 좋습니다.

stringModel.add(2018 as any);
​
// 또는
​
stringModel.add(<any>2018);

Practice

Generic ⇾ Function 

제레릭이 설정된 함수

/// JavaScript ----------------------------------------

function getItemArray(arr, index) {
  return arr[index];
}

function pushItemArray(arr, item) {
  arr.push(item)
}


/// TypeScript ----------------------------------------

function getItemArray(arr:any[], index:number):any {
  return arr[index];
}

function pushItemArray(arr:any[], item:any):void {
  arr.push(item);
}

Generic ⇾ Function 

제레릭이 설정된 함수

/// 제네릭이 설정된 TypeScript 함수

function getItemArray<T>(arr:T[], index:number):T {
  return arr[index];
}

function pushItemArray<T>(arr:T[], item:T):void {
  arr.push(item);
}


const potatoChip_materials = ['어니언'];

getItemArray(potatoChip_materials, 0);                 // '어니언'
pushItemArray<string>(potatoChip_materials, '사워크림'); // ['어니언', '사워크림']

Generic ⇾ Function 

제레릭이 설정된 함수

/// 제네릭이 지정된 타입이 달라 오류 발생

const potatoChip_materials = ['어니언'];
​
// [오류]
// 'string[]' 형식의 인수는 'number[]' 형식의 매개 변수에 할당될 수 없습니다.
// 'string' 형식은 'number' 형식에 할당할 수 없습니다.
pushItemArray<number>(potatoChip_materials, 999);


// --------------------------------------------------------------------------


/// 타입 단언을 사용해 오류를 우회하는 방법

// pushItemArray()에 사용자가 타입을 지정한 경우
pushItemArray<number>(potatoChip_materials as any, 61);

// pushItemArray()에 사용자가 타입을 지정하지 않은 경우
pushItemArray(potatoChip_materials, <any>999);

Practice

Generic ⇾ Function Multi Type 

제레릭이 설정된 함수 멀티 타입 변수

// pairArray 사용자 정의 타입(Type Alias) 정의
type pairArray = [any, any][];

// 멀티 타입 T, M 설정된 함수 pushPairItem 정의
function pushPairItem<T,M>(arr:pairArray, item:[T,M]):pairArray {
  arr.push(item);
  return arr;
}

// piarArray 타입으로 설정된 data 배열 선언
const data:pairArray = [];

// 멀티 타입을 지정한 후, 적절한 타입을 포함하는 데이터 추가
pushPairItem<boolean, string>(data, [false, 'false']);
pushPairItem<number, string>(data, [2019, '이천십구년']);

Generic ⇾ Factory Multi Type 

제레릭이 설정된 팩토리 함수 멀티 타입 변수

// 클래스 Model
class Model {
  constructor(public options:any) {}
}

// 팩토리 함수
function create<T, U>( C: new (U) => T, options: U ):T {
  return new C(options);
}
​
// create() 팩토리 함수에 Model, string[] 멀티 타입 설정
create<Model, string[]>(Model, ['class type']);

Practice

Generic ⇾ Extends Type Variable 

제레릭이 설정된 타입 변수 확장

// Model 클래스
class Model<T> {
  
  constructor(private _data:T[] = []) {}
  
  add(item:T):void {
    this._data.push(item);
  }

}

// Model 초기화 팩토리 함수
function initializeModel<T extends Model<U>, U>(C: new () => T, items:U[]):T {
  const c = new C();
  items.forEach(item => c.add(item));
  return c;
}

// 사용 예시
initializeModel<Model<string>, string>(Model, ['타입', '변수', '상속']);

Practice

모듈

Export Module 

모듈 내보내기

/// DOM/Events.ts

export function on(
  el         : Element|Document,
  type       : string,
  handler    : (e:Event)=>void,
  is_capture : boolean = false
):void {
  el.addEventListener(type, handler, is_capture);
}

export function off(
  el         : Element|Document,
  type       : string,
  handler    : (e:Event)=>void,
  is_capture : boolean = false
):void {
  el.removeEventListener(type, handler, is_capture);
}

Import Module 

모듈 불러오기

/// app.ts

import { on, off } from './Dom/events';

on(document, 'click', (e) => document.body.style.background = '#912f03');

Import * as Module 

모듈 이름 사용자 임의 지정

/// app.ts

import * as Events from './Dom/events';


let changeBodyBGColor = () => document.body.style.background = '#912f03';

Events.on(document, 'click', changeBodyBGColor);

Events.off(document, 'dblclick', changeBodyBGColor);

Export Default Module 

기본 모듈 내보내기

/// DOM/Selectors.ts

function el(selector:string, context:HTMLElement|Document = document): HTMLElement {
  return context.querySelector(selector);
}

function els(selector:string, context:HTMLElement|Document = document): NodeList {
  return context.querySelectorAll(selector);
}

export default { el, els };

Import Default Module 

기본 모듈 불러오기

/// app.ts

import Dom from './Dom/selectors';
import { on, off } from './Dom/events';

on(document, 'click', (e) => Dom.el('body').style.background = '#912f03');

Practice

Module Bundler

데코레이터

Decorator 

데코레이터

// 데코레이터는 
// 클래스, 속성, 메서드, 접근 제어자, 매개변수 등에 
// 사용할 수 있는 특별한 함수입니다.
function Component(target:Function) {
  console.log(target);
  console.log(target.prototype);
}



// 데코레이터를 사용해 
// 클래스 TabsComponent를 정의할 수 있습니다.
// 선언된 데코레이터 함수를 사용할 때는 
// 데코레이터 이름 앞에 @를 붙입니다.
@Component
class TabsComponent {}

Practice

Decorator Factory 

데코레이터 팩토리

// 데코레이터 팩토리
function Component(value:string) {
  console.log(value);
  
  // 데코레이터 함수
  return function(target:Function) {
    console.log(target);
    console.log(target.prototype);
  }
  
}

// 데코레이터 팩토리를 사용하면 값을 전달할 수 있습니다.
@Component('tabs')
class TabsComponent {}

// TabsComponent 객체 생성
const tabs = new TabsComponent();

Practice

Multi Decorator 

멀티 데코레이터

/// 데코레이터는 하나 이상 연결해 사용할 수 있습니다. 
/// 멀티 데코레이터는 수평 또는 수직 나열하여 사용할 수 있습니다.


// 데코레이터 수평 나열
@Size @Color class Button {}


// 데코레이터 수직 나열
@Size
@Color
class Button {}

Execution Flow 

데코레이터 실행 흐름

// Size 데코레이터 팩토리
function Size() {
  console.log('Size(): 평가됨');
  // Size 데코레이터
  return function(target:any, prop:string, desc:PropertyDescriptor){
    console.log('Size(): 실행됨');
  }
}
​
// Color 데코레이터 팩토리
function Color() {
  console.log('Color(): 평가됨');
  // Color 데코레이터
  return function(target:any, prop:string, desc:PropertyDescriptor){
    console.log('Color(): 실행됨');
  }
}
​
// Button 클래스 정의
class Button {

  // 메서드에 멀티 데코레이터 적용
  @Size()
  @Color()
  isPressed() {}

}

1

2

3

4

Practice

Class Decorator 

클래스 데코레이터

// Component 데코레이터
function Component(target:Function) {
  
  // 프로토타입 객체 참조
  let $ = target.prototype;

  // 프로토타입 객체 확장
  $.type = 'component';
  $.version = '0.0.1';

}
​
// Component 데코레이터 사용
@Component
class TabsComponent {};

// TabsComponent 객체 인스턴스 생성
const tabs = new TabsComponent();

// 데코레이터로 설정된 프로토타입 확장은 
// 타입 단언(Type Assertion) 필요
console.log((tabs as any).type); // 'component' 출력

Practice

Class Decorator 

데코레이터를 통한 사용자 정의 클래스 재정의

// Component 데코레이터
// function Component(target) {
function Component<T extends new (...args:any[]) => {}>(target:T) {

  // 재정의
  return class extends target {
    // 속성
    type:string = 'component';
    version:string = '0.1.0';
    // 메서드
    open() { console.log('탭 활성화') }
    close() { console.log('탭 비활성화') }
  }
​
}
​
// Component 데코레이터 사용
@Component
class TabsComponent {
  
  el:HTMLElement;
  
  constructor(el:HTMLElement) {
    this.el = el;
  }
  
  open() { console.log('사용자 정의 탭 open 메서드') }
​
};
​
// TabsComponent 객체 인스턴스 생성
const tabs = new TabsComponent(document.querySelector('.tabs'));

console.log((tabs as any).type); // 'component' 출력
console.log((tabs as any).open()); // '탭 활성화' 출력

Practice

Class Decorator Factory 

클래스 데코레이터 팩토리

// ComponentType 타입 앨리어스 정의
type ComponentType = { 
  el:string;
  [prop:string]:any;
};

// Component 데코레이터 팩토리
function Component(options:ComponentType) {
  const _el = document.querySelector(options.el);
  // Component 데코레이터
  return function Component<T extends new (...args:any[]) => {}>(target:T) {
    return class extends target {
      el:HTMLElement = <HTMLElement>_el;
    }
  }
}
​
// Component 데코레이터 사용
@Component({
  el: '.tabs'
})
class TabsComponent {};
​
// TabsComponent 객체 인스턴스 생성
const tabs = new TabsComponent();
console.log((tabs as any).el);

Practice

Method Decorator 

메서드 데코레이터

// Write 데코레이터 팩토리
function Write(able:boolean = true) {
  // Write 데코레이터
  return function(t:any,p:string,d:PropertyDescriptor) { 
    d.writable = able;
  }
}

// Button 클래스
class Button {
  // 생성자
  constructor(public el:HTMLButtonElement){}
  // Write 데코레이터 사용
  // false 전달 ⟹ 쓰기 불가
  @Write(false)
  disable(){ this.el.setAttribute('disabled', 'disabled'); }
}

// Button 객체 인스턴스 생성 및 변수 참조
const btn = new Button( <HTMLButtonElement>document.querySelector('.button') );

// [오류]
// 쓸 수 없는 메서드를 쓰려고 하였기에 쓸 수 없다고 오류 메시지를 출력합니다.
// Uncaught TypeError: 
// Cannot assign to read only property 'disable' of object '#<Button>'
btn.disable = function() { console.log(this); };

Practice

Access Modifiers Decorator 

접근 제어자 데코레이터

// Configurable 데코레이터 팩토리
function Configurable(remove:boolean) {
  // Configurable 데코레이터
  return function (t:any, k:string, d:PropertyDescriptor){
    d.configurable = remove;
  }
}

// Rectangle 클래스
class Rectangle {
  
  private _width:number;
  private _height:number;
  
  constructor(
    w:number, 
    h:number, 
    public color:string = '#000'
  ) {
    this._width = w;
    this._height = h;
  }
  
  // 접근 제어자 데코레이트 사용
  @Configurable(false)
  get width() {
    return this._width;
  }

  @Configurable(false)
  get height() {
    return this._height;
  }
  
}

Access Modifiers Decorator 

접근 제어자 데코레이터

// Rectangle 객체 인스턴스 생성
const rect = new Rectangle(400, 210);

// 속성 제거 가능
delete rect.color;


/// 생성된 객체 인스턴스의 접근 제어자 속성(configurable: false 설정)을 
/// 제거하려 시도하면 오류가 발생합니다.

// [오류]
// delete 연산자의 피연산자는 읽기 전용 속성일 수 없습니다.
delete rect.width;

Practice

Property Decorator 

속성 데코레이터

// logProp 속성 데코레이터 정의
function logProp(t:any, p:string) {

  // 속성 값
  let v = t[p];

  // getter 속성
  let getter = function() {
    console.log(`GET: ${p} => ${v}`);
    return v;
  }

  // setter 속성
  let setter = function(new_v:any) {
    v = new_v;
    console.log(`SET: ${p} => ${v}`);
  }


  // 속성 값을 제거한 후,
  if (delete t[p]) {
    // 새 속성 정의
    Object.defineProperty(t, p, {
      // getter 속성 연결
      get: getter,
      // settert 속성 연결
      set: setter,
      enumerable: true,
      configurable: true
    });
  }

}
​
// Button 클래스
class Button {

  // logProp 속성 데코레이터 사용
  @logProp
  type:string = 'button';
  
  @logProp
  version:string = '0.0.2';

  // 생성자
  constructor(public el:HTMLButtonElement){}
​
}

Property Decorator 

속성 데코레이터

// Button 객체 인스턴스 생성
const btn = new Button( document.querySelector('.button') as HTMLButtonElement );

// btn.type 읽기 시도
console.log(btn.type);

// btn.type 쓰기 시도
btn.type = '버튼';



/// 사용자가 속성을 읽거나 쓰려고 
/// 시도할 때 마다 로그를 남깁니다.

// 콘솔 출력 결과

/*

GET: type => button
typescript.js:54 button
​

typescript.js:20 SET: type => 버튼

*/

Practice

Parameter Decorator 

매개변수 데코레이터

// Log 매개변수 데코레이터
function Log(t:any, p:string, i:number) {
  console.log(t.name);
  console.log(`
    매개변수 순서: ${i}, 
    멤버 이름: ${p === undefined ? 'constructor' : p}
  `);
}
​
// Button 클래스
class Button {

  el:HTMLButtonElement;
  color:string;

  // 생성자 함수 내에 Log 데코레이터 사용
  constructor(
    @Log el:HTMLButtonElement, 
    @Log color:string = 'transparent'
  ) {
    this.el = el;
    this.color = color;
  }

  // 스태틱 메서드 내에 Log 데코레이터 사용
  static initialize(
    @Log els:NodeList, 
    @Log color:string = 'transparent'
  ){
    return [].slice.call(els).map(el => new Button(el, color));
  }

}

// Button.initialize() 스태틱 메서드 사용
const btns = Button.initialize( document.querySelectorAll('.button'), '#900' );

Parameter Decorator 

매개변수 데코레이터

/// Console에 출력된 결과는 다음과 같습니다. 
/// 매개변수의 순서가 역순인 것이 눈에 띕니다.
/// 
/// 버튼 객체 인스턴스들을 순환 생성 처리하는 
/// initialize 스태틱 메서드 다음에 
/// constructor 생성자 함수가 출력됩니다.


/*

Button
매개변수 순서: 1, 멤버 이름: initialize

Button
매개변수 순서: 0, 멤버 이름: initialize

Button
매개변수 순서: 1, 멤버 이름: constructor

Button
매개변수 순서: 0, 멤버 이름: constructor

*/

Practice

가이드북

Made with Slides.com