개발/타입스크립트

TypeScript로 블록체인 만들기(3)

TTOLBE 2022. 12. 29. 14:36

Classes

이 글은 노마드 코더의 타입스크립트 무료 강의를 보고 노트 정리를 한 글입니다.
강의링크

4.0 Classes

이번에는 객제지향 타입스크립트에 대해 배워보자.

타입스크립트로 클래스를 만드는 방법은 아래와 같다.


class Player {
  constructor(
    private firstName:string,
    private lastName:string,
    public nickName:string
  )
}

타입스크립트에서는 constructor에서 public, private 속성을 부여할 수 있다.


const ttolbe= new Player("Ttolbe","Na","Tolby")

ttolbe.firstName;

ttolbe.nickName;

위와 같이 오브젝트를 만들고 firstName 속성을 불러오면 에러가 나지만 nickName은 에러가 나지 않는다. firstName은 private 속성을 지녔고 nickName은 public속성을 지녔기 때문이다.

타입스크립트가 가진 기능 중 하나는 abstract class 이다. 추상 클래스는 다른 클래스에게 상속할수 있다. 하지만 새로운 인스턴스를 만들 수는 없다.


abstract class User{
    constructor(
    private firstName:string,
    private lastName:string,
    public nickName:string
  ){}
}

class Player extends User{

}

const ttolbe= new User("Ttolbe","Na","Tolby")

위와 같이 User를 새로 만들려고 하면 오류가 발생한다.

추상 클래스 안에는 추상 메소드를 넣을 수 있다.


abstract class User{
    constructor(
    private firstName:string,
    private lastName:string,
    public nickName:string
  ){}
      getFullName(){
      return `${this.firstName} ${this.lastName}`
    }
}

class Player extends User{

}
const ttolbe= new Player("Ttolbe","Na","Tolby")

ttolbe.getFullName()

추상 메소드로 클래스 안의 private 속성에 접근 가능하다. 한편 이 메소드를 아래와 같이 private 속성으로 변경하면 getFullName() 함수는 실행할수 없어 에러가 난다.


abstract class User{
    constructor(
    private firstName:string,
    private lastName:string,
    public nickName:string
  ){}
     private getFullName(){
      return `${this.firstName} ${this.lastName}`
    }
}

또 다른 속성에는 protected가 있다. protected를 사용하면 public 속성 처럼 값에는 접근 가능하지만, 값을 변경 할 수는 없다.


abstract class User{
    constructor(
    protected firstName:string,
    private lastName:string,
    public nickName:string
  ){}
      getFullName(){
      return `${this.firstName} ${this.lastName}`
    }
}

class Player extends User{

}
const ttolbe= new Player("Ttolbe","Na","Tolby")

ttolbe.nickName="nick"

ttolbe.firstName="Me"

위와 같이 코드를 짜면 nickName은 변경 가능하지만, firstName은 변경 되지 않아 에러가 발생한다.

4.1 Recap

지금 까지 배운 것을 응용해 해시맵을 만들어보자.


type Words={
  [key:string]:string
}

//키와 값이 둘다 string인 object type

class Dict{
  private words:Words,
  constructor(){
    this.words={}
  }
  add(word:Word){//클래스를 타입처럼 사용가능
    if(this.words[word.term]===undefined){
      this.words[word.term]=word.def
    }
  }
  def(term:string){
    return this.words[term]
  }
}

class Word{
  constructor(
    public readonly term:string,//term은 수정 불가능하게
    public def:string
  ){}
}

const kimchi = new Word("Kimchi","Korean Food")

const dict=new Dict()

dict.add(kimchi)

dict.def("kimchi")

4.2 Interfaces

인터페이스는 타입과 비슷하지만, 두가지 부분에서 다르다. 우선 지금 까지 타입을 선언하던 방법을 되돌아보자.


type Player={
  nickName:string,
  health:number
}

const Ttolbe:Player={
  nickName:"TTolbe",
  health:10
}

type Food=string

const kimchi:Food="kimchi"

또한 타입을 옵션으로 제한 할 수도 있다. 옵션 외의 값을 입력하면 틀렸다고 한다.


type Team="red"|"blue"|"yellow"
//Team의 옵션 제한
type Health=1|5|10

type Player={
  nickName:string,
  team:Team,
  health:Health
}

const Ttolbe:Player={
  nickName:"TTolbe",
  team:"red",
  health:10
}

인터페이스는 위와는 타입 선언 방식이 다르다.


type Team="red"|"blue"|"yellow"

type Health=1|5|10

interface Player {
  nickName:string,
  team:Team,
  health:Health
}

const Ttolbe:Player={
  nickName:"TTolbe",
  team:"red",
  health:10
}

인터페이스는 타입스크립트에게 오브젝트의 형태를 알리는 방식이다. 다만 타입은 인터페이스보다 활용도가 높다. 인터페이스는 오브젝트외에 다른 변수 모양은 특정 할 수 없다.

인터페이스는 아래와 같이 상속 또한 가능하다.


interface User{
  name:string
}

interface Player extends User{
}

const ttolbe:Player={
  name:"ttolbe"
}

위의 작업을 타입으로 적용해보자.


type User={
  name:string
}

type Player=User&{
}

const ttolbe:Player={
  name:"ttolbe"
}

인터페이스 역시 class처럼 readonly의 속성을 부여 가능하다.


type User={
  readonly name:string
}

인터페이스의 또 다른 특징은 속성의 축적이다.


interface User{
  name:string
}

interface User{
  lastName:string
}

interface User{
  health:number
}

const ttolbeUser:User={
  name:"Ttolbe",
  lastName:"Kim",
  health:10
}

위와 같이 동일 인터페이스를 여러개 쓰면 타입스크립트가 알아서 합쳐준다. 하지만 타입은 속성의 축정이 불가능하다.

4.3 Interfaces part Two

다시 한번 추상 클래스에 대해 복습해보자.


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(name:string){
    return `Hello ${name}. My name is ${this.fullName()}`
  }
}

추상 클래스는 상속 받는 클래스가 어떻게 행동해야하는지 알려주기 위해 존재한다.

다시 인터페이스로 돌아가자면, 우선 인터페이스는 자바스크립트로 컴파일 되지 않기 때문에 가볍다. 여기서 추상 클래스를 인터페이스로 변환해보자.


interface class User{
  firstName:string,
  lastName:string,
  sayHi(name:string):string;
  fullName():string
}

class Player implements User{
  constructor(
    private firstName:string,
    private lastName:string
  ){}
  fullName(){
    return `${this.firstName} ${this.lastName}`
  }
  sayHi(name:string){
    return `Hello ${name}. My name is ${this.fullName()}`
  }
}

위의 코드를 적으면 에러가 난다. 인터페이스를 상속할 때는 property를 private 또는 protected 으로 설정할 수 없기 때문이다.

4.4 Recap

인터페이스는 원하는 메소드와 property를 클래스가 가지도록 강제 할 수 있다. 또한 인터페이스는 자바스크립트로 컴파일되지 않는다. 추상 클래스와 비슷한 보호를 제공하나, 자바스크립트에서는 보이지 않는 것이다.

또 다시 인터페이스와 타입의 차이를 복습해보자.


type PlayerA={
  name:string
}

const playerA:PlayerA={
  name:"ttolbe"
}

interface PlayerB {
  name:string
}

const playerB:PlayerB={
  name:"ttolbe"
}

위의 코드에서는 타입과 인터페이스 둘다 수행하는 역할이 같기 때문에 차이가 없어보인다. 하지만 타입스크립트에서 허용하는 둘의 기능은 좀 다르다.

일단 상속 방식의 차이를 보자.


type PlayerA={
  name:string
}

type PlayerAA= PlayerA & {
  lastName:string
}
//타입의 상속을

const playerA:PlayerAA={
  name:"ttolbe",
  lastName:"Kim"
}

interface PlayerB {
  name:string
}

interface PlayerBB extends PlayerB{
  lastName:string
}

const playerB:PlayerBB={
  name:"ttolbe",
  lastName:"kim"
}

둘이 비슷하지만 타입은 하나의 타입에 또다른 property를 추가할수 없다. 하지만 인터페이스는 가능하다.


interface PlayerB {
  name:string
}

interface PlayerB{
  lastName:string
}

const playerB:PlayerBB={
  name:"ttolbe",
  lastName:"kimm"
}

원한다면 인터페이스와 타입 모두 추상 클래스를 대신할수 있다.


type PlayerA={
  firstName:string
}

class User implements PlayerA{
  constructor(
    public firstName:string
  ){}
}

interface PlayerB {
  firstName:string
}

class User implements PlayerB{
  constructor(
    public firstName:string
  ){}
}

4.5 Polymorphism

전에 배웠듯이 다형성은 다른 모양의 코드를 가질 수 있게 해주는 것이며 다형성을 이루는 법은 제네릭을 사용하는 것이다.

브라우저에서 쓰는 로컬스토리지와 유사한 API를 만들어보자.


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 stringsStorage= new LocalStorage<string>()

stringsStorage.set("key","hello")

stringsStorage.get("key")//result is string

const booleanStorage=new LocalStorage<boolean>()

booleanStorage.set("key",true)

booleanStorage.get("key")//result is boolean