9주차
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
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())