Classes
기존의 Java, Python에서의 클래스 개념과 별반 다를게 없습니다. 따라서 편하게 작성해 보도록 하겠습니다.
class Player {
constructor(
private firstName: string,
private lastName: string,
public nickname: string
) {}
}
const hyunseo = new Player("hyunseo", "las", "현서");
// error
hyunseo.firstName
다음과 같이 Class의 생성자 안에 private, public을 써줄 수 있는데, 이는 JS에서는 없는 기능입니다. 위 코드가 트랜스 파일된 결과를 보
면
"use strict";
var Player = /** @class */ (function () {
function Player(firstName, lastName, nickname) {
this.firstName = firstName;
this.lastName = lastName;
this.nickname = nickname;
}
return Player;
}());
var hyunseo = new Player("hyunseo", "las", "현서");
//# sourceMappingURL=index.js.map
private, public은 찾아볼 수 없습니다. 단지 TS가 우리를 보호해 주는 것 뿐입니다. firstName을 외부에서 변경하려고 한다면, 오류를 띄우고 오직 public만 혀용할 수 있게 말이죠. 또한 이 뿐만 아니라 상속과, 추상클래스, 추상 메소드도 지원하게 됩니다.
// cannot make Instance
abstract class User {
constructor(
protected firstName: string,
protected lastName: string,
protected nickname: string
) {}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
abstract getNickName(): void;
}
class Player extends User {
getNickName() {
console.log(this.nickname);
}
}
const hyunseo = new Player("hyunseo", "las", "현서");
hyunseo.getFullName();
추상 메소드를 선언하고, Player에서 이를 구현하였습니다. 거희 대부분의 객체지향 언어하고 동일합니다.
Recap
여기서 저희는 사전을 만들어 보도록 하겠습니다. 이를 하기 위해서는 Dict Class를 다음과 같이 만들어 주어야 합니다.
type Words = {
[key: string]: string;
};
class Dict {
private words: Words;
}
여기서 Words타입을 만들었는데, 이는 string만을 property로 가지는 object라는 것을 말해준 겁니다.
이제 Word Class를 통해 음식의 term과 deff을 받아서 코드를 완성해 봅시다.
type Words = {
[key: string]: string;
};
class Dict {
private words: Words;
constructor() {
this.words = {};
}
add(word: Word) {
if (this.words[word.term] === undefined) {
this.words[word.term] = word.deff;
}
}
deff(term: string) {
return this.words[term];
}
showDic() {
return JSON.stringify(this.words, null, 2);
}
}
class Word {
constructor(public term: string, public deff: string) {}
}
const kimchi = new Word("kimchi", "한국의 음식");
const kimchi2 = new Word("kimchi", "한국의 음식");
const bullgogi = new Word("bullgogi", "한국의 음식2");
const dict = new Dict();
dict.add(kimchi);
dict.add(kimchi2);
dict.add(bullgogi);
console.log(dict.deff("kimchi"));
console.log(dict.showDic());
이에 대한 결과는
다음과 같이 정확하게 나오게 됩니다. 또한 Word의 term, deff을 readonly로 바꾸고 Dict Class에 static Method를 추가한 version은 아래와 같습니다.
type Words = {
[key: string]: string;
};
class Dict {
private words: Words;
constructor() {
this.words = {};
}
add(word: Word) {
if (this.words[word.term] === undefined) {
this.words[word.term] = word.deff;
}
}
deff(term: string) {
return this.words[term];
}
showDic() {
return JSON.stringify(this.words, null, 2);
}
static hello() {
return "hello";
}
}
class Word {
constructor(
public readonly term: string,
public readonly deff: string
) {}
}
const kimchi = new Word("kimchi", "한국의 음식");
const kimchi2 = new Word("kimchi", "한국의 음식");
const bullgogi = new Word("bullgogi", "한국의 음식2");
const dict = new Dict();
dict.add(kimchi);
dict.add(kimchi2);
dict.add(bullgogi);
console.log(dict.deff("kimchi"));
console.log(dict.showDic());
Interfaces
우선 type으로 예제를 하나 작성해 보도록 하겠습니다.
type Team = "red" | "blue" | "yellow";
type Health = 1 | 5 | 10;
type Player = {
nickname: string;
team: Team;
healthBar: Health;
};
const hyunseo: Player = {
nickname: "hyunseo",
team: "yellow",
healthBar: 10,
};
interface는 오로지 오브젝트의 모양을 타입스크립트에게 설명해 주기 위해서 사용되는 키워드 입니다. 따라서 다음과 같이 위의 type을 interface로 바꾸어도 동작하겠지요
type Team = "red" | "blue" | "yellow";
type Health = 1 | 5 | 10;
interface Player {
nickname: string;
team: Team;
healthBar: Health;
}
const hyunseo: Player = {
nickname: "hyunseo",
team: "yellow",
healthBar: 10,
};
interface Health = string과 같이 못한다는 것입니다. 즉 type이 확장 가능성이 더 많다고 볼 수 있습니다. 반면 interface는 제한적입니다. 이 둘을 비교하기 위해 비교 예제를 들어보겠습니다. 먼저 interface를 사용한 예제입니다.
interface User {
name: string;
}
interface Player extends User {}
const hyunseo: Player = {
name: "hyunseo",
};
마치 class를 사용하는 것 같죠? 이를 type을 통해서도 구현할 수 있습니다.
type User = {
name: string;
};
type Player = User & {
};
const hyunseo: Player = {
name: "hyunseo",
};
다음과 같이 &을 통해 연결시켜 줄 수 있습니다. 다시한번 말하지만, interface는 어느 Object의 형태를 결정할 떄만 사용합니다. 그리고 인터페이스는 인터페이스를 상속해서 쓸 수 있습니다. 또한 합치는데 type보다 우의를 점합니다. 다음 예제를 봅시다.
interface User {
readonly name: string;
}
interface User {
readonly lastName: string;
}
interface User {
readonly health: number;
}
const hyunseo: User = {
name: "hyunseo Lee",
lastName: "hyunseo",
health: 10,
};
다음과 같이 User Interface는 name, lastname, health가 합쳐진 체로 유지됩니다. 반면 type은 이런 것이 불가능합니다.
Interfaces - 2
우선 추상클래스와 메소드에 대해서 복습을 하는 차원에서 예제를 하나 작성해 보도록 하겠습니다.
abstract class User {
constructor(
protected firstName: string,
protected lastName: string
) {}
abstract sayHi(name: string): string;
abstract fullName(): string;
}
class Player extends User {
fullName() {
return `${this.firstName} ${this.lastName}`;
}
sayHi(nam: string) {
return `Hello ${name}. My name is ${this.fullName()}`;
}
}
이렇게 하면 js에 추상클래스가 남아있게 됩니다. 하지만 이를 interface로 바꾸어 봅시다.
interface User {
firstName: string;
lastName: string;
sayHi(name: string): string;
fullName(): string;
}
class Player implements User {
constructor(
public firstName: string,
public lastName: string
) {}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
sayHi(nam: string) {
return `Hello ${name}. My name is ${this.fullName()}`;
}
}
다음과 같이 implements를 사용하여 클래스가 특정 인터페이스를 충족하는지 확인시켜줄 수 있습니다.또한 원래라면 User Class가 남아있어야 하는데, interface로 작성하면 남아있지 않게 되어, Player Class만 존재하게 되고 코드가 간결해지고, 파일의 크기도 줄어들게 됩니다. 하지만 문제점중 하나는, private property들을 사용하지 못한다는 점입니다. 또한 추상클래스에서 생성자에서 변수로 바로 대입해주는 기능을 제공했지만, 이를 제공하지 않는다는 단점이 있습니다.
또한 2개의 interface를 implements할 수 도 있습니다.
interface User {
firstName: string;
lastName: string;
sayHi(name: string): string;
fullName(): string;
}
interface Human {
health: number;
}
class Player implements User, Human {
constructor(
public firstName: string,
public lastName: string,
public health: number
) {}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
sayHi(nam: string) {
return `Hello ${name}. My name is ${this.fullName()}`;
}
}
Recap
type과 interface에는 많은 차이점이 있습니다. 아래의 예시처럼 확장성을 들 수 있습니다.
type PlayerA = {
name: string;
};
type PlayerAA = PlayerA & {
lastName: string;
};
const playerA: PlayerAA = {
name: "hyunseoA",
lastName: "xxx",
};
///////
interface PlayerB {
name: string;
}
interface PlayerBB extends PlayerB {
lastName: string;
}
interface PlayerBB {
health: number;
}
const playerB: PlayerBB = {
name: "hyunseoB",
lastName: "xxx",
health: 10,
};
또한 둘다 class를 implements할 수 있습니다.
type PlayerA = {
firstName: string;
};
interface PlayerB {
firstName: string;
}
class User implements PlayerA {
constructor(public firstName: string) {}
}
class User2 implements PlayerB {
constructor(public firstName: string) {}
}
정리하자면 type과 interface는 매우 유사해, 자유롭게 써도 괜찮고, interface의 거의 대부분의 기능들은 type에 있지만, 위에서 말한 차이점 들이 존재한다는 점만 기억하면 될 것 같습니다.
Polymorphism
여기서 저희는 브라우저에서 쓰는 로컬 스토리지 API와 비슷한 API를 만들어 보면서 interface의 다형성에 대해 알아보도록 하겠습니다.
interface SStorage<T> {
[key: string]: T;
}
class LocalStorage<T> {
private storage: SStorage<T> = {};
set(key: string, value: T) {
this.storage[key] = value;
}
remove(key: string) {
delete this.storage[key];
}
get(key: string): T {
return this.storage[key];
}
clear() {
this.storage = {};
}
}
const stringStorage = new LocalStorage<string>();
stringStorage.set("hello", "world");
console.log(stringStorage.get("hello"));
const booleanStorage = new LocalStorage<boolean>();
// booleanStorage.get("xxx");
type으로 하는 다형성과 비슷합니다. 그냥 class에서 Generic을 사용해서 interface로 넘겨주고 사용하는 예시를 주목해 볼 필요가 있을 겁니다.
'Web > TypeScript' 카테고리의 다른 글
Typescript Practice - Block Chain Project -implements (0) | 2022.05.08 |
---|---|
Typescript Practice - Block Chain Project - Basic (0) | 2022.05.07 |
Typescript Practice - Functions (0) | 2022.05.07 |
Typescript Practice - OverView (0) | 2022.05.07 |