ECC

9주차

avocado8 2024. 5. 18. 22:29

 

4. Classes and Interfaces

4.0 Classes

class Player {
    constructor(
        private firstName: string, //private는 JS에서 사용되지 않음
        private lastName: string,
        public nickname: string
    ) {}
}

const cado = new Player("cado", "babo", "카도");

cado.firstName //오류 - firstName은 private
cado.nickname //오류x

 

abstract class(추상 클래스) : 다른 클래스가 상속받을 수 있는 클래스

추상 클래스는 인스턴스를 만들 수 없음

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

class Player extends User {
}

const cado = new Player("cado", "babo", "카도");
cado.getFullName() //메소드도 상속받음

 

추상 메소드

추상 클래스 안에서 만들 수 있는 메소드. 메소드를 구현하지 않고 메소드의 call signature만 작성

추상클래스를 상속받는 모든 것들에서 구현해야 함

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

property를 private로 만들면 해당 클래스를 상속하였더라도 접근할 수 없음.

* 접근제어자 (JS에서는 X)

private : User 클래스의 인스턴스나 메소드에서만 접근가능 (자식클래스에서도 사용x)

protected : 필드가 외부로부터 보호되나 자식 클래스에서는 사용되기를 바랄 때 사용

public : 어디서든 접근가능

 

4.1 Recap

type Words = { //string만을 property로 가지는 Object
    [key: string] : string
}

class Dict {
    private words: Words
    constructor(){
        this.words = {}
    }
    add(word: Word){
        if(this.words[word.term] === undefined){ //현재 dict에 없는 단어
            this.words[word.term] = word.def
        }
    }
    def(term: string){
        return this.words[term]
    }
    remove(term:string){
    }
}

class Word {
    constructor(
        public term: string,
        public def: string
    ){}
}

const cado = new Word("cado", "아기고래")

const dict = new Dict()
dict.add(cado);
dict.def("cado");

 

4.2 Interfaces

type Team = "red" | "blue" | "yellow" //type이 특정 값만 가지도록 제한
type Health = 1 | 5 | 10

type Player = {
    nickname: string,
    team: Team,
    health: Health
}
const cado: Player = {
    nickname: "cado",
    team: "red",
    health: 5
}

 

Interface: object의 모양을 TS에게 설명해주기 위해서만 사용됨.

type Team = "red" | "blue" | "yellow" //type이 특정 값만 가지도록 제한
type Health = 1 | 5 | 10

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

const cado: Player = {
    nickname: "cado",
    team: "red",
    health: 5
}

 

인터페이스끼리 상속 가능

interface User {
    name: string
}
interface Player extends User {
}
const nana : Player = {
    name: "nana"
}

 

같은 동작을 type으로 구현

type User = {
    name: string
}
type Player = User & {
}
const nana : Player = {
    name: "nana"
}

 

4.3 Interfaces part 2

JS에서는 추상클래스 개념을 사용하지 않기에 TS에서 abstract class를 만들어도 JS에서는 일반적인 클래스로 변환됨

인터페이스는 컴파일하면 JS로 변환되지 않고 사라짐 -> 코드가 가벼워짐

//추상클래스로 구현
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}` //protected이므로 접근 가능
    }
    sayHi(name:string){
        return `Hello ${name}. My name is ${this.fullName()}`
    }
}
interface User {
    firstName: string,
    lastName: string,
    sayHi(name:string):string
    fullName():string
}
class Player implements User {
    //interface의 property 구현 필요
    constructor( //interface 상속할 때는 public이어야 함
        public firstName: string,
        public lastName: string
    ){}
    fullName(){
        return `${this.firstName} ${this.lastName}` //protected이므로 접근 가능
    }
    sayHi(name:string){
        return `Hello ${name}. My name is ${this.fullName()}`
    }
}

 

여러 interface 상속 가능

interface User {
    firstName: string,
    lastName: string,
    sayHi(name:string):string
    fullName():string
}
interface Human {
    health: number
}
class Player implements User, Human {
    //interface의 property 구현 필요
    constructor( //interface 상속할 때는 public이어야 함
        public firstName: string,
        public lastName: string,
        public health: number
    ){}
    fullName(){
        return `${this.firstName} ${this.lastName}` //protected이므로 접근 가능
    }
    sayHi(name:string){
        return `Hello ${name}. My name is ${this.fullName()}`
    }
}

 

인터페이스도 타입으로 사용 가능

function makeUser(user: User){
    return "hi"
}
makeUser({
    firstName: 'cado',
    lastName: 'babo',
    fullName: ()=>"xx",
    sayHi: (name) => "string"
})

 

타입은 한번 정의하면 재정의할 수 없지만(프로퍼티 추가 등) (하려면 이전 타입 & {} 해서 새 타입 만들어야 함)

인터페이스는 가능 (여러 번 써서 프로퍼티 추가 가능)

 

4.5 Polymorphism

interface Storage { //js의 웹 스토리지 api를 위한 interface (기존에 정의되어 있음)
}
interface SStorage<T> {
    [key: string]: T
}

class LocalStorage <T>{ //T 타입의 local storage
    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>() //string type의 local storage
stringsStorage.get("key") //string
stringsStorage.set("hello", "cado")

const boolStorage = new LocalStorage<boolean>()
boolStorage.get("xxx") //boolean
boolStorage.set("hello", true)

 

 

5. TypeScript Blockchain

5.1 Targets

프로젝트 생성

npm init -y
npm i -D typescript

 

tsconfig.json 작성

{
  "include": [
    "src" //확인할 TS파일 위치
  ],
  "compilerOptions": {
    "outDir": "build", //JS파일이 생성될 디렉터리
    "target": "ES6" //JS 코드 버전
  }
}

 

package.json 수정

{
  "name": "typechain",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "build": "tsc"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "typescript": "^5.4.5"
  }
}

 

5.2 Lib Configuration

TS에게 코드 동작 환경을 알려줌

TS는 해당 동작 환경에 따른 API 타입 정의를 제공

{
  "include": [
    "src" //확인할 TS파일 위치
  ],
  "compilerOptions": {
    "outDir": "build", //JS파일이 생성될 디렉터리
    "target": "ES6", //JS 코드 버전
    "lib": ["ES6", "DOM"] //DOM: 브라우저 환경
  }
}

 

5.3 Declaration Files (정의 파일)

타입 정의가 명시된 파일.

확장자: d.ts

 

아래처럼 정의한 js파일의 함수들을 다른 ts파일에서 사용하려면 ts에게 타입 정의를 알려주어야 할 필요가 있음

//myStorage.js
export function init(config){
  return true
}
export function exit(code){
  return code + 1
}

 

아래처럼 myStorage.d.ts 작성

interface Config {
  url: string;
}
declare module "myPackage"{
  function init(config:Config): boolean;
  function exit(code:number): number;
}

 

5.4 JSDoc

https://jsdoc.app/

 

Use JSDoc: Index

Index Getting started Getting started with JSDoc A quick start to documenting JavaScript with JSDoc.Using namepaths with JSDoc A guide to using namepaths with JSDoc.Command-line arguments to JSDoc About command-line arguments to JSDoc.Configuring JSDoc wit

jsdoc.app

JS코드에 comment를 작성하여 TS가 확인할 수 있도록 함

코멘트를 추가하는 것이므로 코드 동작과는 무관. JS를 사용 중이나 TS의 보호장치를 쓰고 싶을 때 사용

//myPackage.js
// @ts-check
/**
 * INITIALIZE
 * @param {object} config 
 * @param {boolean} config.debug
 * @param {string} config.url
 * @returns {boolean}
 */
export function init(config){
  return true
}

/**
 * EXITS THE PROGRAM
 * @param {number} code 
 * @returns {number}
 */
export function exit(code){
  return code + 1
}

 

5.5 Blocks

npm i -D ts-node
npm i -D nodemon

ts-node: 빌드 없이 ts를 실행할 수 있게 해주는 개발 의존성 패키지

nodemon: 코드를 auto-refresh해서 서버 재시작하지 않아도 반영되도록 함

 

package.json 수정

"scripts": {
    "build": "tsc",
    "dev": "nodemon --exec ts-node src/index",
    "start": "node build/index.js"
  },

 

Block class

import * as crypto from "crypto"

interface BlockShape {
  hash: string;
  prevHash: string;
  height: number; //block 위치
  data: string;
}
class Block implements BlockShape {
  public hash: string;
  constructor(
    public prevHash: string,
    public height: number,
    public data: string,
  ){
    this.hash = Block.calculateHash(prevHash, height, data) //오류발생
  }
  static calculateHash(prevHash:string, height:number, data:string){ //static: instance가 없어도 실행가능한 함수
    const toHash = `${prevHash}${height}${data}`
  }
}

 

5.6 DefinitelyTyped

해당 패키지에 대한 type 설치

npm i -D @types/(패키지이름)

 

nodejs의 모든 패키지 타입 설치

npm i -D @types/node

 

crypto 패키지 사용해 Block 설계

import * as crypto from "crypto"

interface BlockShape {
  hash: string;
  prevHash: string;
  height: number; //block 위치
  data: string;
}
class Block implements BlockShape {
  public hash: string;
  constructor(
    public prevHash: string,
    public height: number,
    public data: string,
  ){
    this.hash = Block.calculateHash(prevHash, height, data)
  }
  static calculateHash(prevHash:string, height:number, data:string){ //static: instance가 없어도 실행가능한 함수
    const toHash = `${prevHash}${height}${data}`
    return crypto.createHash("sha256").update(toHash).digest("hex")
  }
}

 

5.7 Chain

class Blockchain {
  private blocks: Block[]
  constructor(){
    this.blocks = []
  }
  private getPrevHash(){
    if (this.blocks.length === 0) return ""
    return this.blocks[this.blocks.length - 1].hash
  }
  public addBlock(data: string){
    const newBlock = new Block(this.getPrevHash(), this.blocks.length+1, data)
    this.blocks.push(newBlock)
  }
  public getBlocks(){
    return [...this.blocks] //보안 문제 해결 - 새 배열 리턴
  }
}

 

최종 코드

import * as crypto from "crypto"

interface BlockShape {
  hash: string;
  prevHash: string;
  height: number; //block 위치
  data: string;
}
class Block implements BlockShape {
  public hash: string;
  constructor(
    public prevHash: string,
    public height: number,
    public data: string,
  ){
    this.hash = Block.calculateHash(prevHash, height, data)
  }
  static calculateHash(prevHash:string, height:number, data:string){ //static: instance가 없어도 실행가능한 함수
    const toHash = `${prevHash}${height}${data}`
    return crypto.createHash("sha256").update(toHash).digest("hex")
  }
}


class Blockchain {
  private blocks: Block[]
  constructor(){
    this.blocks = []
  }
  private getPrevHash(){
    if (this.blocks.length === 0) return ""
    return this.blocks[this.blocks.length - 1].hash
  }
  public addBlock(data: string){
    const newBlock = new Block(this.getPrevHash(), this.blocks.length+1, data)
    this.blocks.push(newBlock)
  }
  public getBlocks(){
    return [...this.blocks] //보안 문제 해결 - 새 배열 리턴
  }
}

const blockchain = new Blockchain()
blockchain.addBlock("First one")
blockchain.addBlock("Second one")
blockchain.addBlock("Third one")

console.log(blockchain.getBlocks())

실행 결과