상위집합
트랜스파일러
TypeScript 파일(ts)은 웹 브라우저에서 바로 해석될 수 없습니다.
브라우저에서 해석 가능한 언어인 JavaScript로 변경되어야
브라우저는 이를 인식하고 해석할 수 있습니다.
TypeScript를 JavaScript로 변환해야
웹 브라우저가 처리 가능 합니다.
~ ❯ 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.
디렉토리 구성
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.
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
*/
타입 추론
let coffee_type; // `any` 타입
coffee_type = '콜드브루';
coffee_type = 9112304129312;
타입 추론을 하지 않는 경우
let coffee_type:string;
coffee_type = '콜드브루';
coffee_type = 9112304129312;
/*
[ts] '9120304123' 형식은 'string' 형식에 할당할 수 없습니다.
let coffee_type: string
*/
명시적 타입 선언
// 명시적으로 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;
프리미티브 타입
// 명시적으로 any 타입 지정
let product_id:any = 124981;
// any 유형이 설정되었으므로 어떤 유형도 값으로 할당 가능
product_id = 'p9023412';
// -------------------------------------------
// 암시적으로 any 타입 지정
let product_id;
product_id = 124981;
product_id = 'p9023412';
애니 타입
let members = ['이권', '감장겸', '장도일']; // 암시적 타입 선언(타입 추론)
// [오류]
// [ts] 'number[]' 형식은 'string[]' 형식에 할당할 수 없습니다.
// 'number' 형식은 'string' 형식에 할당할 수 없습니다.
// let members: string[]
members = [9, 13, 26];
배열 타입
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, 'ㅇ'];
터플 타입
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);
열거형 타입
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
매개변수 타입
// 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, '스노우보드');
유니온 타입
// 유니온(합병) 타입은 파이프(|) 기호를 사용해 타입을 지정
function setInfo(id:number|string, name:string) {
return { id, name };
}
let product_one = setInfo(120912, '스노우보드');
let product_two = setInfo('P-014921', 'VR 헤드셋');
리턴 타입
// 리턴 값 타입이 명시적으로 설정되지 않는 함수
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;
}
함수 식 타입
// 변수에 함수 값을 할당하는 식(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);
객체 타입
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(){}
};
객체 타입
// 타입으로 설정되지 않은 객체의 속성을 새롭게 추가할 경우, 다음과 같은 오류 메시지를 출력합니다.
// ——————————————————————————————————————————————————————————————————————————————————————
// [오류]
// [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(){};
낫싱 타입
// 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 = '미네랄';
}
엄격한 null 체크 설정
// tsconfig.json
"strictNullChecks": false
// 오류가 출력되지 않습니다.
let assign_name:string = null;
if (!assign_name) {
assign_name = '미네랄';
}
네버 타입
// 항상 오류 발생
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') })();
사용자 정의 타입
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[]
};
타입 단언
// 타입 어설션(단언)
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;
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 {}
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 {}
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('파주 공장'));
클래스 상속
// 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 = '출판사 웹서버';
}
클래스 상속 ⟹ 생성자 / 수퍼
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;
}
}
클래스 상속 ⟹ 생성자 / 수퍼
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);
}
}
게터 / 세터
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); // '푸른 식물'
클래스 속성 / 메서드
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));
추상 클래스
// 추상 클래스
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();
추상 클래스
// 클래스 ⟸ 추상 클래스 상속
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 올리브 네트웍스 웹사이트 개편'
싱글턴 패턴
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();
읽기 전용 속성
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 = '노멀 원';
인터페이스 정의
// 인터페이스 Button 정의
interface ButtonInterface {
onInit():void;
onClick():void;
}
// -------------------------
// 커스텀 타입과 유사해 보임
type ButtonType = {
onInit():void;
onClick():void;
}
인터페이스 vs 커스텀 타입
/// 인터페이스는 병합 가능
interface ButtonInterface {
onInit():void;
onClick():void;
}
...
interface ButtonInterface {
onToggle():void;
}
// ----------------------------------
/// 커스텀 타입은 병합 불가능
type ButtonType = {
onInit():void;
onClick():void;
}
// [오류]
// 'ButtonType' 식별자가 중복되었습니다.
type ButtonType = {
onToggle():void;
}
인터페이스 : 클래스 이행 규칙
// 인터페이스 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' 형식에 없습니다.
}
// 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 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 ButtonInterface {
// 속성 이름 뒤에 ? 기호가 붙으면 옵션 속성이 됩니다.
onInit?():void;
onClick():void;
}
class ButtonComponent implements ButtonInterface {
// onInit 메서드가 설정되지 않아도 오류를 발생하지 않습니다.
onClick() { console.log('버튼 클릭') }
}
인터페이스 읽기전용 설정
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';
인터페이스 × 클래스
interface ButtonInterface {
onInit?():void;
onClick():void;
}
class ButtonComponent implements ButtonInterface {
// 클래스의 경우, 인터페이스에 명시 되지 않은
// 속성을 추가해도 오류가 발생하지 않습니다.
type:string = 'button';
disabled:boolean = false;
constructor() {}
onClick() { console.log('버튼 클릭') }
}
인터페이스 × 객체
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 ButtonInterface {
onInit?():void;
onClick():void;
// 인덱스 시그니처
[prop:string]: any;
}
const button:ButtonInterface = {
// 인터페이스에 설정되지 않은 속성이 추가되어도
// 오류가 발생하지 않습니다.
type: 'button',
disabled: false,
onClick() { console.log('버튼 클릭') }
};
인터페이스 × 함수 타입
// 인터페이스를 연결하지 않은 함수의 경우, 매개변수, 리턴 값을 설정합니다.
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);
};
인터페이스 × 함수 타입
// 주의할 점은 인터페이스가 설정된 함수의 매개변수, 리턴 값 타입을 임의로 변경하면 오류가 발생합니다.
// [오류]
// '(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);
};
인터페이스 확장
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;
}
인터페이스 다중 확장
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;
}
인터페이스 ↦ 클래스 확장
// 인터페이스를 콤마(,)로 구분하여 다중 확장할 수 있습니다.
class ImageButton implements ImageButtonInterface {
readonly _type:string;
readonly _url:string;
readonly _size:number;
onClick(){}
getUrl() { return this._url; }
small() {}
medium() {}
large() {}
}
인터페이스 ↦ 객체
// [오류]
// '{}' 형식은 'ImageButtonInterface' 형식에 할당할 수 없습니다.
// '_url' 속성이 '{}' 형식에 없습니다.
let imageButton:ImageButtonInterface = {};
// --------------------------------------------------------
// 오류를 해결하려면 인터페이스가 요구하는 이행 조건을 모두 맞춰야 합니다.
let imageButton:ImageButtonInterface = {
_url: '',
_size: 14,
_type: 'button',
getUrl() { return this._url; },
setUrl(url:string) { },
small() { },
medium() { },
large() { },
onClick() { },
};
인터페이스 ↦ 객체
// 제네릭(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 설정') };
어떤 타입도 아이템으로 받을 수 있는 모델 클래스
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 = [];
}
}
모델 클래스를 상속 받은 오브젝트모델 클래스
// 특정 데이터 타입을 규정한 Model 클래스를 사용하고자 할 수도 있을 겁니다.
// 이런 경우 클래스 상속을 이용한 새로운 클래스를 생성해 문제를 해결할 수 있습니다.
// 예를 들어 객체 타입만 데이터로 추가할 수 있도록 하기 위한 ObjectModel 클래스를
// 정의한다면 다음과 같이 구성합니다.
class ObjectModel extends Model {
constructor(data:object[]=[]) {
super(data);
}
add(item:object):void {
super.add(item);
}
}
// 하지만 클래스 상속을 사용하면 별도의 자료 타입을 받고자 하는 클래스를
// 추가해야 하고 중복되는 코드를 양산하기에 불편합니다. 바로 이런 경우에
// 유용하게 사용할 수 있는 것이 `제네릭` 입니다.
제레릭이 설정된 클래스
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 = [];
}
}
제레릭이 설정된 클래스 | 타입 단언
const stringModel = new Model<string>();
stringModel.add('흔들의자');
// [오류]
// '2018' 형식의 인수는 'string' 형식의 매개 변수에 할당될 수 없습니다.
stringModel.add(2018);
// --------------------------------------------------------
// TypeScript 프로그래밍 과정에서 부득이하게 정해진 타입이 아닌 경우를
// 사용해야 하는 경우가 종종 발생합니다. 이런 경우 타입 어설션 문법을 사용해
// 컴파일 과정의 타입 검사를 우회할 수 있습니다만, 꼭 필요한 경우에만
// 사용하는 것이 좋습니다.
stringModel.add(2018 as any);
// 또는
stringModel.add(<any>2018);
제레릭이 설정된 함수
/// 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);
}
제레릭이 설정된 함수
/// 제네릭이 설정된 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, '사워크림'); // ['어니언', '사워크림']
제레릭이 설정된 함수
/// 제네릭이 지정된 타입이 달라 오류 발생
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);
제레릭이 설정된 함수 멀티 타입 변수
// 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, '이천십구년']);
제레릭이 설정된 팩토리 함수 멀티 타입 변수
// 클래스 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']);
제레릭이 설정된 타입 변수 확장
// 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, ['타입', '변수', '상속']);
모듈 내보내기
/// 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);
}
모듈 불러오기
/// app.ts
import { on, off } from './Dom/events';
on(document, 'click', (e) => document.body.style.background = '#912f03');
모듈 이름 사용자 임의 지정
/// 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);
기본 모듈 내보내기
/// 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 };
기본 모듈 불러오기
/// app.ts
import Dom from './Dom/selectors';
import { on, off } from './Dom/events';
on(document, 'click', (e) => Dom.el('body').style.background = '#912f03');
데코레이터
// 데코레이터는
// 클래스, 속성, 메서드, 접근 제어자, 매개변수 등에
// 사용할 수 있는 특별한 함수입니다.
function Component(target:Function) {
console.log(target);
console.log(target.prototype);
}
// 데코레이터를 사용해
// 클래스 TabsComponent를 정의할 수 있습니다.
// 선언된 데코레이터 함수를 사용할 때는
// 데코레이터 이름 앞에 @를 붙입니다.
@Component
class TabsComponent {}
데코레이터 팩토리
// 데코레이터 팩토리
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();
멀티 데코레이터
/// 데코레이터는 하나 이상 연결해 사용할 수 있습니다.
/// 멀티 데코레이터는 수평 또는 수직 나열하여 사용할 수 있습니다.
// 데코레이터 수평 나열
@Size @Color class Button {}
// 데코레이터 수직 나열
@Size
@Color
class Button {}
데코레이터 실행 흐름
// 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
클래스 데코레이터
// 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' 출력
데코레이터를 통한 사용자 정의 클래스 재정의
// 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()); // '탭 활성화' 출력
클래스 데코레이터 팩토리
// 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);
메서드 데코레이터
// 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); };
접근 제어자 데코레이터
// 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;
}
}
접근 제어자 데코레이터
// Rectangle 객체 인스턴스 생성
const rect = new Rectangle(400, 210);
// 속성 제거 가능
delete rect.color;
/// 생성된 객체 인스턴스의 접근 제어자 속성(configurable: false 설정)을
/// 제거하려 시도하면 오류가 발생합니다.
// [오류]
// delete 연산자의 피연산자는 읽기 전용 속성일 수 없습니다.
delete rect.width;
속성 데코레이터
// 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){}
}
속성 데코레이터
// 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 => 버튼
*/
매개변수 데코레이터
// 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' );
매개변수 데코레이터
/// Console에 출력된 결과는 다음과 같습니다.
/// 매개변수의 순서가 역순인 것이 눈에 띕니다.
///
/// 버튼 객체 인스턴스들을 순환 생성 처리하는
/// initialize 스태틱 메서드 다음에
/// constructor 생성자 함수가 출력됩니다.
/*
Button
매개변수 순서: 1, 멤버 이름: initialize
Button
매개변수 순서: 0, 멤버 이름: initialize
Button
매개변수 순서: 1, 멤버 이름: constructor
Button
매개변수 순서: 0, 멤버 이름: constructor
*/