강의영상
https://www.youtube.com/watch?v=0UMmCbcZid0&list=PLOSNUO27qFbsI9bAIVitBcq-klZae5GMi
TypeScript
안전하고 예측 가능한 코드 구현에 초점을 맞춘 자바스크립트의 상위 집합(Superset) 언어이다.
기존 자바스크립트에 타입 시스템을 적용한 것이다.
타입스크립트로 작성된 코드는 타입스크립트 컴파일러(tsc)를 통해 자바스크립트로 변환된다.
타입 시스템 외에도 Decorators와 같은 다양한 기능을 제공한다.
타입스크립트는 자바스크립트 타입 체계에 정적 타입(static typed)를 적용한다.
자바스크립트는 동적 타입 체계(dynamic typed)의 언어로 변수에 타입을 지정하지 않는다. 인터프리터를 이용한 라인 단위의 변역을 실행한다.
동적 타입 체계는 프로그램에 유연성을 제공하지만 변수에 할당된 값을 읽어 사용하는 단계에서 문제가 발생할 수 있다.
타입스크립트는 변수, 파라미터에 타입을 지정할 수 있으며 선언된 타입의 값이 아닌 경우 에러를 발생시킨다.
- Primitive: number, string, boolean, null, undefined (ES5), symbol (ES6), void
- Object : functions, classes, arrays
Type System(타입 시스템)
- Type Annotation
- Parameter Annotation
- Result Type Annotation
Type Inference(타입 추론)
정적으로 타입을 명시하지 않고 대입(assign)하는 값을 통해 타입을 유추해 결정하는 것
변수의 선언문과 실행문을 함께 사용할 때 실행된다.
타입을 명시하지 않아도 타입 추론에 의해 타입이 결정된다.
let name = 'Kim'; // name : stirng 으로 지정
name = 20; // error
변수를 선언하고 다른 코드에서 값을 대입하면 any 타입의 변수가 된다.
let name;
name = 'Kim'; // name : any 으로 지정
name = 20; // ok
any 타입으로 지정된 변수는 타입스크립트 컴파일 시점에 해당 변수에 대한 타입 검사를 수행하지 않는다.
타입스크립트 개발 환경 구성
- 필수 모듈: typescript.ts
- node 설치를 기본 전제로 npm(또는 yam)을 이용하 타입스크립트를 설치하면 tsc를 통해 .ts 파일에 대한 컴파일이 가능하다.
- ts-node를 설치하면 컴파일과 실행을 한번에 수행할 수 있다.
=> transcomplie
Transcomplie
.ts -> (tsc complie) -> .js
$ tsc hello.ts
위 명령을 수행하면 트랜스컴파일 결과로 hello.js 파일 생성
$ node hello.js
node를 통해 js 파일을 실행
tsconfig 파일 생성
$ tsc --init
package.json 파일 생성
$ npm init -y
nodemon, concurrently 설치
$ npm install nodemon concurrently
- nodemon: 개발 과정 중 스크립트 모니터링 도구. 소스의 변경을 감지하고 서버를 재시작한다.
- concurrently: 병행 실행 지원 도구
package.json 스크립트 설정
// package.json
"scripts": {
...
"start:build": "tsc -w", // 타입스크립트 컴파일 과정을 콘솔에서 확인 (watch mode)
"start:run": "nodemon build/index.js", // nodemon을 통해 index.js 실행
"start": "concurrently npm:start:*" // 'npm:start'로 시작하는 명령을 한번에 실행
},
start 스크립트는 build와 run을 한번에 실행하는 것을 의미한다.
npm start 명령 후 콘솔창
// index.ts
console.log("hello");
$ npm start
> ts_workspace@1.0.0 start
> concurrently npm:start:*
[build]
[build] > ts_workspace@1.0.0 start:build
[build] > tsc -w
[build]
오후 9:00:39 - Starting compilation in watch mode...
[build]
[run] hello
[run] [nodemon] clean exit - waiting for changes before restart
[build]
[build] 오후 9:00:40 - Found 0 errors. Watching for file changes.
[run] [nodemon] restarting due to changes...
[run] [nodemon] starting `node build/index.js`
[run] hello
[run] [nodemon] clean exit - waiting for changes before restart
Annotation
Todo List 개발: Data Model 정의
TodoItem.ts
class TodoItem {
// 프로퍼티 선언부가 없어도 생성자에 접근 지정자와 함께 파라미터를 정의하면 프로퍼티로 인식한다.
// id: number;
// task: string;
// complete: boolean
constructor(public id: number, public task: string, public complete: boolean) {
this.id = id;
this.task = task;
this.complete = complete;
}
printDetail(): void {
console.log(
`${this.id}\t${this.task}\t${this.complete ? "\tcomplete" : ""}`
);
}
}
export default TodoItem;
index.ts
import TodoItem from "./TodoItem";
const data = [
{ id: 1, task: "장보기", complete: true },
{ id: 2, task: "TS 학습하기", complete: false },
]
console.log('My Todo List');
for(let i = 0; i < data.length; i++) {
let todoItem: TodoItem = new TodoItem(data[i].id, data[i].task, data[i].complete);
todoItem.printDetail();
}
- data와 i는 선언과 초기화를 동시에 하여 타입 추론에 의해 타입이 결정되었다.
Type Annotation
Variable
변수에 타입을 지정할 수 있다.
변수에 정의한 타입과 다른 타입을 대입하는 코드는 타입스크립트 컴파일러에 의해 컴파일 오류가 발생한다.
Object Literal Annotation
객체 리터럴에 대한 타입 정의는 해당 리터럴 객체가 갖는 프로퍼티에 대한 타입을 나열하는 형태로 정의한다.
Functions
함수를 정의할 때 파라미터와 반환값에 타입을 지정한다.
함수의 종류(선언적 함수, 익명 함수, 람다 함수)에 따라 타입 지정에 대한 표현에 차이가 있다.
함수의 파라미터를 정의할 때 선택적 매개변수(Optional Parameter)를 지정할 수 있다.
// 선언적 함수
function add(n1: number, n2: number): number {
return n1 + n2;
}
// 익명 함수
const add = function(n1: number, n2: number): number {
return n1 + n2;
}
// 람다 함수
const add:(n1: number, n2: number) => number = (n1:number, n2:number) : number => {
return n1 + n2;
}
함수 오버로딩
자바스크립트는 기본적으로 가변인자를 통한 함수 호출이 가능했지만 타입스크립트는 가변인자를 지원하지 않는다.
대신 타입스크립트는 함수 오버로딩을 통해 가변인자와 같은 효과를 구현할 수 있다.
선택적 매개변수는 함수 호출시 전달인자를 선택적으로 보낼 수 있는 기능의 매개변수이다.
이외에 ES6 버전부터 지원하는 기본 매개변수(Default Parameter), 나머지 매개변수(Rest Parameter)가 있다.
function add(firstParam: string, secondParam: string): string;
function add(firstParam: number, secondParam: number): number;
function add(firstParam: any, secondParam: any): any {
return firstParam + secondParam;
}
add(10, 20);
add('10', '20');
Todo List 개발: Todo List Collection
TodoItem은 배열과 맵에 저장하는 방식 두 가지로 구현한다.
data.ts 파일로 분리
export const data = [
{ id: 1, task: "장보기", complete: true },
{ id: 2, task: "TS 학습하기", complete: false },
];
TodoCollection.ts : Array
import TodoItem from "./TodoItem";
class TodoCollection {
private nextId: number = 1;
constructor(public userName: string, public todoItems: TodoItem[] = []) {}
getTodoById(id: number) : TodoItem | undefined {
return this.todoItems.find((item) => item.id === id);
}
addTodo(task: string) : number {
while(this.getTodoById(this.nextId)) {
this.nextId++;
}
this.todoItems.push(new TodoItem(this.nextId, task));
return this.nextId;
}
markComplete(id: number, complete: boolean) : void {
const todoItem = this.getTodoById(id);
if(todoItem) {
todoItem.complete = complete;
}
}
}
export default TodoCollection;
constructor
todoItems의 초기값을 빈 배열로 설정하고 있다. 객체를 생성할 때 todoItems 를 파라미터로 전달하지 않으면 빈 배열이 값이 된다.
파라미터에 접근 제어자를 명시해줌으로써 선언과 할당을 생략하였다.
getTodoById( id )
배열의 find 함수는 es6부터 사용할 수 있다.
배열의 요소를 하나씩 꺼내에 item 변수에 할당하고 item.id === id 이면 해당 item을 반환한다.
find 함수의 결과가 없으면 반환값은 TodoItem이 아니라 undefined 이다.
addTodo( task )
todoItems에서 다음 id(nextId)를 찾아 새로 생성한 TodoItem 객체를 TodoCollection에 추가한다.
markComplete()
todoItems에서 해당 id를 갖는 todoItem의 complete 상태를 변경한다.
index.ts
import TodoItem from "./TodoItem";
import { data } from "./data";
import TodoCollection from "./TodoCollection";
const sampleTodos: TodoItem[] = data.map(
(item) => new TodoItem(item.id, item.task, item.complete)
);
const myTodoCollection = new TodoCollection("My Todo List", sampleTodos);
myTodoCollection.addTodo("JS 학습하기");
myTodoCollection.addTodo("친구 만나기");
myTodoCollection.markComplete(3, true);
console.log(`${myTodoCollection.userName}`);
myTodoCollection.todoItems.forEach((item) => item.printDetail());
[run] My Todo List
[run] 1 장보기 complete
[run] 2 TS 학습하기
[run] 3 JS 학습하기 complete
[run] 4 친구 만나기
Inference Around Functions
함수 파라미터를 정의할 때 타입을 지정하지 않으면 any 타입의 파라미터가 지정된다. (경고 사항)
함수 반환 값에 대한 타입은 return 구문에 따라 타입 추론(Type Inference)이 적용된다.
void
함수의 반환 값이 없을 경우 void 타입의 반환을 정의한다.
void 타입은 변수로 사용될 경우 undefined, null 값만 대입(assign) 가능하다.
never
함수의 반환 값으로 지정할 수 있는 never 타입은 절대 발생하지 않는 값의 타입을 나타낸다. ex) 무한 루프, 예외 throw
never 타입은 어떤 타입의 변수에도 대입될 수 있지만 never 타입에는 어떤 타입의 값도 대입할 수 없다.
let accountBalance: number = 10000;
function overdraftError(message?: string) : never {
throw Error(message);
}
function withdraw(balance: number): number {
if(accountBalance < balance) {
overdraftError('잔액이 부족합니다.');
}
return accountBalance - balance;
}
console.log(withdraw(3000)); // 7000
console.log(withdraw(12000)); // error
Typed Array
배열도 일반 변수처럼 타입의 명시적 지정과 초기화를 통한 타입 지정이 가능하다.
타입의 명시족 혹은 묵시적으로 지정하지 않을 경우 해당 배열 참조 변수는 any 타입으로 지정된다.
배열에 서로 다른 타입의 요소를 저장하는 것이 가능하며 이를 타입으로 지정할 수 있다. -> Union type array
const myArr = ['Typescript', 2020];
//const myArr: (string | number)[] = [...];
myArr[2] = 3030;
myArr[3] = 'Javascript'
Tuples
튜플을 이용하면 배열의 요소 수와 각 요소에 대한 타입을 지정할 수 있다.
튜플은 정해진 길이에 맞지 않으면 에러가 발생한다. 하지만 push() 함수를 이용하면 튜플의 규칙은 무시된다.
서로 다른 타입의 요소를 갖는 배열은 순서에 상관없이 데이터를 넣을 수 있는 반면 튜플은 정해진 순서에 맞게 넣는다.
튜플은 배열보다 저장되는 요소에 순서와 수에 제약을 두고자 할 때 사용한다.
const tuples: [string, number] = ['Kim', 30];
tuples[0] = 'Park'; // ok
tuples[0] = 50; // error: Type 'number' is not assignable to type 'string'.
tuples[1] = 50;
console.log(tuples); // ['Park', 50]
tuples.push(100);
console.log(tuples); // ['Park', 50, 100]
Generic Type
Todo List 개발: Map
TodoCollection.ts : Array -> Map
import TodoItem from "./TodoItem";
class TodoCollection {
private nextId: number = 1;
private itemMap: Map<number, TodoItem>;
constructor(public userName: string, todoItems: TodoItem[] = []) {
this.itemMap = new Map();
todoItems.forEach((item) => this.itemMap.set(item.id, item));
}
getTodoById(id: number) : TodoItem | undefined {
return this.itemMap.get(id);
}
addTodo(task: string) : number {
while(this.getTodoById(this.nextId)) {
this.nextId++;
}
this.itemMap.set(this.nextId, new TodoItem(this.nextId, task));
return this.nextId;
}
markComplete(id: number, complete: boolean) : void {
const todoItem = this.getTodoById(id);
if(todoItem) {
todoItem.complete = complete;
}
}
// includeComplete -> true: 모든 할일 목록
// includeComplete -> false: 완료 목록은 제외한 할일 목록
getTodoItems(includeComplete: boolean): TodoItem[] {
return [...this.itemMap.values()].filter(
(item) => includeComplete || !item.complete
);
}
removeComplete(): void {
this.itemMap.forEach((item) => {
if(item.complete) {
this.itemMap.delete(item.id);
}
});
}
}
export default TodoCollection;
constructor
파라미터에서 todoItems의 접근 제어자를 제거하여 프로퍼티가 아닌 단순 파라미터 처리한다.
생성자 내부에서 forEach 구문을 이용하여 todoItems의 요소들을 Map에 저장한다.
getTodoItems(includeComplete)
Map의 요소들을 배열로 반환하기 위해서 전개 연산자를 사용한다.
반환된 배열에 대해 filter 구문을 이용하여 includeComplete에 대한 처리를 수행한다.
- includeComplete가 true 라면 item.complete와 무관하게 모든 item이 담긴다.
- includeComplete가 false 라면 item.complete이 false인 item이 담긴다.
removeComplete()
완료된 할일 목록을 제거한다.
index.ts
import TodoItem from "./TodoItem";
import { data } from "./data";
import TodoCollection from "./TodoCollection";
const sampleTodos: TodoItem[] = data.map(
(item) => new TodoItem(item.id, item.task, item.complete)
);
const myTodoCollection = new TodoCollection("My Todo List", sampleTodos);
myTodoCollection.addTodo("JS 학습하기");
myTodoCollection.addTodo("친구 만나기");
myTodoCollection.markComplete(3, true);
console.log(`${myTodoCollection.userName}`);
// 모든 할일 목록 출력
myTodoCollection.getTodoItems(true).forEach((item) => item.printDetail());
console.log('===========================================================================')
// 미완료 할일 목록 출력
myTodoCollection.getTodoItems(false).forEach((item) => item.printDetail());
console.log('===========================================================================')
// 완료 할일 목록 제거 후 모든 할일 목록 출력
myTodoCollection.removeComplete();
myTodoCollection.getTodoItems(true).forEach((item) => item.printDetail());
[run] My Todo List
[run] 1 장보기 complete
[run] 2 TS 학습하기
[run] 3 JS 학습하기 complete
[run] 4 친구 만나기
[run] ===========================================================================
[run] 2 TS 학습하기
[run] 4 친구 만나기
[run] ===========================================================================
[run] 2 TS 학습하기
[run] 4 친구 만나기
Generics
재사용 가능한 클래스, 함수를 만들기 위해 다양한 타입에서 사용 가능하도록 하는 것
제네릭을 이용하면 모든 타입의 객체를 다루면서 객체 타입의 무결성을 유지할 수 있다.
제네릭을 통해 클래스나 함수 내부에서 사용되는 특정 데이터의 타입을 외부에서 지정한다.
제네릭이 사용된 대상(클래스, 함수, 인터페이스)은 선언(정의) 시점이 아닌 생성 시점에 사용하는 타입을 결정한다.
타입 파라미터는 관용적으로 T를 사용한다.
제네릭이 적용된 대상은 인스턴스화 될 때 지정된 데이터 타입으로 모든 타입 파라미터의 타입이 지정된다.
타입 파라미터는 특정 타입의 하위 타입으로 제한할 수 있다. ex) <? extends Fruit>: Fruit을 상속한 타입으로 제한
class Box<T> {
constructor(private fruit: T) {}
getFruit(): T {
return this.fruit;
}
}
const box: Box<Orange> = new Box(new Orange());
console.log(box.getFruit().getName());
Type alias
새로운 타입을 정의하는 방법: type alias, interface
type alias를 이용하면 객체, 공동체(Union), 튜플(Tuple), 기본 타입에 타입의 별칭을 생성할 수 있다.
type alias도 제네릭 사용이 가능하며 스스로 참조하는 것도 가능하다.
type MyNumber = number;
const n: MyNumber = 10;
type Container<T> = {value: T};
type User = {name:string, age:number};
const testUser: User = {name:'Kim', age:20};
function printInfo(user: {name:string, age:number}) {
console.log(`User Information - Name : ${user.name}, Age: ${user.age}`);
}
function printInfo(user: User) {
console.log(`User Information - Name : ${user.name}, Age: ${user.age}`);
}
User Interaction (CLI)
Todo List 개발: TodoConsole
ItemCount.ts
export type ItemCount = {
total: number; // 모든 할일 목록 개수
incomplete: number; // 미완료 할일 목록 개수
}
할일 목록 개수의 반환값으로 사용할 객체
TodoCollection.ts - 추가
getItemCount() : ItemCount {
return {
total: this.itemMap.size,
incomplete: this.getTodoItems(false).length,
};
}
TodoConsole.ts
inquirer 설치
document: https://www.npmjs.com/package/inquirer
$ npm i inquirer @types/inquirer
** 실행 중 ERR_REQUIRE_ESM 발생시 아래 버전으로 재설치
$ npm install --save inquirer@^8.0.0
- inquirer: inquirer 기본 모듈
- @types/inquirer: 타입스크립트 환경에서 사용하기 위한 추가 라이브러리
// Commans.ts : 사용자 입력 선택지
export enum Commands {
Quit = 'Quit',
}
import * as inquirer from "inquirer";
import TodoCollection from "../service/TodoCollection";
import TodoItem from "../model/TodoItem";
import {data} from "../data";
import {Commands} from "../model/Commands";
class TodoConsole {
private todoCollection: TodoCollection;
constructor() {
const sampleTodos : TodoItem[] = data.map(
(item) => new TodoItem(item.id, item.task, item.complete)
);
this.todoCollection = new TodoCollection('My Todo List', sampleTodos);
}
displayTodoList(): void {
console.log(
`=====${this.todoCollection.userName}=====` +
`(${this.todoCollection.getItemCounts().incomplete} items todo)`
);
this.todoCollection.getTodoItems(true).forEach(
(item) => item.printDetail()
);
}
promptUser(): void {
console.clear();
this.displayTodoList();
let prompt = inquirer.createPromptModule();
prompt({
type: 'list',
name: 'command',
message: 'Choose option',
choices: Object.values(Commands),
}
).then((answers) => { //입력 받은 후
if(answers['command'] !== Commands.Quit) {
this.promptUser();
}
});
}
}
export default TodoConsole;
constructor
아직 사용자 입력을 직접 받지 않고 생성자에서 테스트 데이터를 추가한다.
displayTodoList()
todoCollection에서 TodoCounts 와 TodoItems 을 받아 콘솔에 출력한다.
promptUser()
inquirer 를 사용하여 사용자 입력을 받기 위한 프롬프트를 구성한다.
옵션은 Literal Object 로 초기화했다.
- type: The type of the prompt. (input, number, confirm, list, rawlist, expand, checkbox, password, editor)
- name: The key to save the answer to the answers-hash.
- message: The message to show to the user.
- choices: The choices of the prompt.
$ node build/index.js
=====My Todo List=====(2 items todo)
1 장보기
2 TS 학습하기
? Choose option (Use arrow keys)
> Quit
Enum type
열거형 타입은 상수(사용자 정의 상수)들을 관리하기 위한 객체로 상수의 집합을 정의한다
일반 객체는 속성의 변경을 허용하지만 열거형은 속성의 변경을 허용하지 않는다.
열거형의 속성은 기본적으로 숫자, 문자열만 허용한다.
ES6부터 const 키워드를 사용하여 사용자 정의 상수를 만들 수 있다.
// 사용자 정의 함수
const korean = 'ko';
const english = 'en';
const japanese = 'jp';
const chinese = 'cn';
type LanguageCode = 'ko' | 'en' | 'jp' | 'cn';
const code: LanguageCode = korean;
// enum(열거형) 타입
enum LanguageCode {
korean = 'ko',
english = 'en',
japanese = 'jp',
chinese = 'cn',
}
const code: LanguageCode = LanguageCode.korean;
값을 초기화하지 않으면 기본적으로 0부터 차례대로 할당된다.
Reverse mapping
통상적으로 키를 통해 값을 꺼내는 것 뿐만 아니라 값을 통해 키를 꺼낼 수 있다.
const 로 열거형을 정의하면 reverse mapping 이 작동하지 않는다.
enum ArrowKey {
Up,
Down,
Left = 10,
Right,
}
console.log(ArrowKey.Up); // 0
console.log(ArrowKey[11]); // Right (reverse mapping)
console.log(ArrowKey); // { '0': 'Up', '1': 'Down', '10': 'Left', '11': 'Right',
// Up: 0, Down: 1, Left: 10, Right: 11 }
Add Functions
Todo List 개발 : Add, Remove, Complete, Show/Hide
TodoConsole.ts - Show/Hide, Add 추가
// Commands.ts
export enum Commands {
Quit = 'Quit',
Toggle = "Show/Hide Completed", // 완료된 항목의 표시 여부 변경
Add = "Add New Tasks", //사용자로부터 task 를 입력받아 할일 목록 추가
}
import * as inquirer from "inquirer";
import TodoCollection from "../service/TodoCollection";
import TodoItem from "../model/TodoItem";
import {data} from "../data";
import {Commands} from "../model/Commands";
class TodoConsole {
private todoCollection: TodoCollection;
private showCompleted: boolean; // 추가
constructor() {
this.showCompleted = true; // 처음에는 모든 할일 목록 표시
...
}
promptUser(): void {
console.clear();
this.displayTodoList();
let prompt = inquirer.createPromptModule();
prompt({
type: 'list',
name: 'command',
message: 'Choose option',
choices: Object.values(Commands),
}
).then((answers) => {
switch(answers['command']) {
case Commands.Toggle:
this.showCompleted = !this.showCompleted;
this.promptUser();
break;
case Commands.Add:
this.promptAdd();
break;
case Commands.Quit:
this.promptUser();
break;
}
});
}
promptAdd(): void {
console.clear();
let prompt = inquirer.createPromptModule();
prompt({
type: 'input',
name: 'add',
message: 'Enter task :',
}).then((answers) => {
if(answers['add'] !== "") {
this.todoCollection.addTodo(answers["add"]);
}
this.promptUser();
});
}
}
export default TodoConsole;
showCompleted
- true -> 모든 할일 목록 표시 (초기값)
- false -> 미완료 할일 목록만 표시
- 사용자 메뉴( promptUser() )에서 Commands.Toggle(Show/Hide Completed)를 선택하면 상태가 바뀐다.
promptUser()
Commands.Toggle(Show/Hide Completed) 추가
promptAdd()
type: 'input'
input 형태로 문자열을 입력받아 TodoItem 추가
$ node build/index.js
=====My Todo List=====(2 items todo)
1 장보기 complete
2 TS 학습하기
? Choose option
Quit
Show/Hide Completed
> Add New Tasks
$ node build/index.js
? Enter task : 테스트
$ node build/index.js
=====My Todo List=====(2 items todo)
1 장보기 complete
2 TS 학습하기
3 테스트
? Choose option
> Quit
Show/Hide Completed
Add New Tasks
TodoConsole.ts - Remove, Complete 추가
// Commands.ts
export enum Commands {
Quit = 'Quit',
Toggle = "Show/Hide Completed", // 완료된 항목의 표시 여부 변경
Add = "Add New Tasks", // 사용자로부터 task 를 입력받아 할일 목록 추가
Complete = "Complete Task", // 완료 항목 체크
Purge = "Remove Completed Task", // 완료된 항목 제거
}
import * as inquirer from "inquirer";
import TodoCollection from "../service/TodoCollection";
import TodoItem from "../model/TodoItem";
import {data} from "../data";
import {Commands} from "../model/Commands";
class TodoConsole {
...
promptUser(): void {
console.clear();
this.displayTodoList();
let prompt = inquirer.createPromptModule();
prompt({
type: 'list',
name: 'command',
message: 'Choose option',
choices: Object.values(Commands),
}
).then((answers) => {
switch(answers['command']) {
case Commands.Toggle:
this.showCompleted = !this.showCompleted;
this.promptUser();
break;
case Commands.Add:
this.promptAdd();
break;
case Commands.Purge:
this.todoCollection.removeComplete();
this.promptUser();
break;
case Commands.Complete:
if(this.todoCollection.getItemCounts().incomplete > 0) { // 미완료 할일이 있을 때
this.promptComplete();
} else {
this.promptUser();
}
break;
case Commands.Quit:
this.promptUser();
break;
}
});
}
promptComplete(): void {
console.clear();
let prompt = inquirer.createPromptModule();
prompt({
type: 'checkbox',
name: 'complete',
message: 'Mark Tasks Complete',
choices: this.todoCollection.getTodoItems(this.showCompleted).map(
(item) => ({
name: item.task,
value: item.id,
checked: item.complete,
})
),
}).then((answers) => {
let completedTasks = answers['complete'] as number[]
this.todoCollection.getTodoItems(true).forEach((item) => {
this.todoCollection.markComplete(item.id, completedTasks.find((id) => id === item.id) != undefined)
}
)
this.promptUser();
});
}
}
export default TodoConsole;
promptComplete()
choices: 체크 박스의 항목. name, value, checked 를 속성으로 가지는 객체의 배열을 지정
- name: 제목(title)
- value: 값
- checked: 체크 여부
let completedTasks = answers['complete'] as number[]
this.todoCollection.getTodoItems(true).forEach((item) => {
this.todoCollection.markComplete(item.id, completedTasks.find((id) => id === item.id) != undefined)
}
)
- answers['complete'] as number[]: 체크 항목의 value 배열(answers['complete'])은 any[] 타입이다. 현재 value로 지정한 item.id 는 number 타입이므로 Type Assertion을 통해 number[] 타입으로 받는다.
- this.todoCollection.getTodoItems(true).forEach(...): todoCollection 으로부터 모든 할일 목록을 가져와서 차례대로 각 item에 접근한다. 체크 항목과 비교하여 item의 complete 값을 변경한다.
- completedTasks.find(...): 해당 item의 id가 completedTasks(체크 항목)에 존재하면 find 함수는 해당 id를 반환하고, 존재하지 않으면 undefined를 반환한다.
$ node build/index.js
? Mark Tasks Complete (Press <space> to select, <a> to toggle all, <i> to invert selection,
and <enter> to proceed)
>(*) 장보기
( ) TS 학습하기
Union Type, Type Guard
Union Type
타입스크립트는 타입의 조합을 통해 새로운 타입을 정의할 수 있으며 Union Type도 그중 하나이다.
Union Type은 타입 선언에 하나 이상의 타입을 지정하고 해당 타입 중 하나임을 나타낸다.
Union Type의 정의는 | 연산자를 이용한다.
Union Type의 멤버 사용은 정의된 모든 타입의 공통 멤버들만 사용할 수 있다.
type Rectangle = {
area: number;
color: string;
drawing(): void;
}
type Line = {
length: number;
color: string;
drawing(): void;
}
function print(shape: Rectangle | Line) {
console.log(shape.color);
shape.drawing();
console.log(shape.area); // error: 'area'는 Rectangle 에만 존재한다.
}
Type Guard
Type Guard는 특정 영역(블록) 안에서 해당 변수의 타입을 한정하는 기능이다.
사용자가 정의하거나 number, string, boolean, symbol의 경우 typeof 연산자를 이용한다.
Union Type은 각 타입이 갖는 고유 멤버를 사용할 수 없다.
특정 영역에서 각 타입이 갖는 고유 멤버를 사용하기 위해 Type Guard를 이용한다.
function test(arg: number[] | string) {
if(typeof arg === "string") {
arg.substring(1); // string의 고유 멤버
} else {
arg.push(10); // Array의 고유 멤버
}
}
function isString(data: string | number[]): data is string {
return (<string>data).split !== undefined; // split -> string의 고유 멤버
}
let data: string | number[] = 'TypeScript';
if(isString(data)) {
data.split('');
} else {
// number //
}
TsSorter 개발
- 입력 값: 숫자 배열(number[]), 문자열(string)
- 정렬 방식: Bubble Sort 오름차
class TsSorter {
constructor(private collection: number[] | string) {}
sort(): number[] | string {
const { length } = this.collection;
if(typeof this.collection === "string"){
let characters: string[] = this.collection.split("");
for(let i = 0; i < length; i++) {
for(let j = 0; j < length - i - 1; j++) {
if(characters[j].toLowerCase() > characters[j+1].toLowerCase()) {
//swap
const temp = characters[j];
characters[j] = characters[j+1];
characters[j+1] = temp;
}
}
}
this.collection = characters.join(''); // string[] -> string
} else {
for(let i = 0; i < length; i++) {
for(let j = 0; j < length - i - 1; j++) {
if(this.collection[j] > this.collection[j+1]) {
//swap
const temp = this.collection[j];
this.collection[j] = this.collection[j+1];
this.collection[j+1] = temp;
}
}
}
}
return this.collection;
}
}
export default BubbleSorter;
constructor
this.collection: 정렬 대상. number[] 와 string 의 Union Type
sort()
this.collection 에 대한 정렬 수행
Type Guard(typeof / instanceof)를 사용하여 collection 이 number[] 타입인지, string 타입인지에 따라 다르게 정렬 수행
문자열의 경우 문자열 배열로 변환한 뒤 정렬하고 다시 string 으로 변환
Interface
Interface 개요
인터페이스는 여러 타입의 속성으로 이루어진 새로운 타입을 정의하는 방법이다.
인터페이스도 클래스와 마찬가지로 프로퍼티와 메서드를 갖지만, 인터페이스를 이용한 인스턴스는 생성할 수 없다.
인터페이스에 정의하는 메서드는 모두 추상메서드이지만 abstract 키워드를 사용하지 않는다.
인터페이스를 이용하면 변수, 함수, 클래스에 타입을 지정할 수 있다.
Optional property, readonly property
인터페이스의 프로퍼티는 선택적 옵션과 읽기 전용 옵션을 지정할 수 있다.
기본적으로 인터페이스에 정의하는 모든 프로퍼티는 필수지만 ? 를 사용하여 선택적 프로퍼티로 지정할 수 있다.
인터페이스에 정의하는 특정 프로퍼티에 대해 readonly 를 사용하여 상수처럼 사용할 수 있다.
- const: 변수 선언에 사용
- readonly: 프로퍼티 정의에 사용
Interface와 Class
클래스는 implements 키워드를 통해 인터페이스를 구현할 수 있다.
인터페이스를 구현한 클래스는 모든 프로퍼티를 포함하고 인터페이스에 정의된 추상메서드를 구현해야 한다.
하나의 클래스는 다수의 인터페이스를 구현(implements) 할 수 있고 인터페이스간 확장(extends)이 가능하다.
Duck typing
특정 인터페이스에서 정의한 프로퍼티나 메서드를 갖는 클래스는 해당 인터페이스를 구현한 것으로 인정하며 이를 덕 타이핑(duck typing)이라고 한다.
객체의 프로퍼티나 메서드가 객체의 타입을 결정한다.
interface Animal {
name: string;
color: string;
move(): void;
}
class Car {
name: string = "car";
constructor(public color: string) {}
move(): void {
console.log("boong-boong");
}
}
let v: Animal = new Car("black"); // ok
TsSorter: 기능 확장
숫자, 문자에 대한 정렬 외에 특정 객체에 대한 정렬 구현
인터페이스를 통해 확장 가능한 구조 구성
피정렬 객체: NumbersCollection, CharactorsCollection, CustomerCollection
- 피정렬 객체에 대한 타입(인터페이스)를 정의한다.
- 인터페이스에는 compare(), swap() 를 포함하며 TsSorter에서 각 메서드를 호출하여 정렬을 수행한다.
TsSorter - Sortable 인터페이스 추가
interface Sortable {
length: number;
compare(leftIndex: number, rightIndex: number): boolean;
swap(leftIndex: number, rightIndex: number): void;
}
class TsSorter {
constructor(private collection: Sortable) {}
sort(): void {
const { length } = this.collection;
for(let i = 0; i < length; i++) {
for(let j = 0; j < length - i - 1; j++) {
if(this.collection.compare(j, j+1)) {
this.collection.swap(j, j+1);
}
}
}
}
printCollection(): void {
console.log(this.collection);
}
}
export default TsSorter;
- TsSorter는 객체를 생성할 때 Sortable 타입의 정렬 대상 collection을 받는다.
- sort(): Sortable의 각 메서드를 통해 정렬을 수행한다. (추상화)
- 정렬 대상 클래스에 implements Sortable 를 명시하지 않아도 인터페이스 내의 모든 프로퍼티, 메서드를 포함하면 Duck typing에 의해 Sortable 타입으로 추론이 가능하다.
NumbersCollection
데이터 타입: number[]
class NumbersCollection {
constructor(private data: number[]) {}
get length(): number {
return this.data.length;
}
compare(leftIndex: number, rightIndex: number): boolean {
return this.data[leftIndex] > this.data[rightIndex]; // 오름차순 정렬
}
swap(leftIndex: number, rightIndex: number): void {
const temp = this.data[leftIndex];
this.data[leftIndex] = this.data[rightIndex];
this.data[rightIndex] = temp;
}
}
export default NumbersCollection;
CharactersCollection
데이터 타입: string
lass CharactersCollection {
constructor(private data: string) {}
get length(): number {
return this.data.length;
}
compare(leftIndex: number, rightIndex: number): boolean {
return this.data[leftIndex].toLowerCase() > this.data[rightIndex].toLowerCase();
}
swap(leftIndex: number, rightIndex: number): void {
const characters: string[] = this.data.split('');
const temp = characters[leftIndex];
characters[leftIndex] = characters[rightIndex];
characters[rightIndex] = temp;
this.data = characters.join('');
}
}
export default CharactersCollection;
CustomerCollection
데이터 타입: Customer[]
// Customer.ts
export class Customer {
constructor(public id: string, public name: string) {
}
}
import {Customer} from "./Customer";
class CustomerCollection {
constructor(private data: Customer[]) {}
get length() {
return this.data.length;
}
// Customer.name 을 통한 비교
compare(leftIndex: number, rightIndex: number): boolean {
return this.data[leftIndex].name.localeCompare(this.data[rightIndex].name) > 0;
}
swap(leftIndex: number, rightIndex: number): void {
const temp = this.data[leftIndex];
this.data[leftIndex] = this.data[rightIndex];
this.data[rightIndex] = temp;
}
}
export default CustomerCollection;
index.ts
import TsSorter from "./TsSorter";
import NumbersCollection from "./model/NumbersCollection";
import CharactersCollection from "./model/CharactersCollection";
import CustomerCollection from "./model/CustomerCollection";
import {Customer} from "./model/Customer";
let numbersCollection: NumbersCollection = new NumbersCollection([10, -7, 55, 3, 21]);
let charactersCollection: CharactersCollection = new CharactersCollection('TypeScript');
let customerCollection: CustomerCollection = new CustomerCollection([
new Customer('A111', 'Kim'),
new Customer('B111', 'Park'),
new Customer('C111', 'Andy'),]);
let sorter = new TsSorter(numbersCollection);
sorter.sort();
console.log(sorter.printCollection());
sorter = new TsSorter(charactersCollection);
sorter.sort();
console.log(sorter.printCollection());
sorter = new TsSorter(customerCollection);
sorter.sort();
console.log(sorter.printCollection());
$ node build/index.js
NumbersCollection { data: [ -7, 3, 10, 21, 55 ] }
undefined
CharactersCollection { data: 'ceipprSTty' }
undefined
CustomerCollection {
data: [
Customer { id: 'C111', name: 'Andy' },
Customer { id: 'A111', name: 'Kim' },
Customer { id: 'B111', name: 'Park' }
]
}
'Advance II > TypeScript' 카테고리의 다른 글
| [TS] Index Signature (0) | 2023.09.12 |
|---|---|
| [TS] 타입스크립트 (0) | 2023.09.11 |