타입스크립트로 블록체인 만들기 – 노마드 코더 Nomad Coders

이 포스트는 노마드 코더의 강의 <타입스크립트로 블록체인 만들기> 강의를 공부하며 정리한 노트입니다.



# 1.5

자바스크립트의 문제점

  • 자바스크립트는 에러를 보여주지 않으려고 노력한다… (유연하다 = 예측 불가능하다)

    [1, 2, 3, 4] + false; // 결과 : '1,2,3,4false'
    
    ////
    
    function devide(a, b) {
      return a + b;
    }
    
    devide("xxx"); //실행결과 : NaN => 실행이 된다. 실행이 되기 전, 안전장치가 없다
    
    ////
    
    const nico = { name: "nico" };
    nico.hello(); // TypeError: nico.hello is not a function => 실행이 되고 에러가 난다.
    

타입스크립트는 “Type Safety” 를 제공한다.



# 2.1

  1. typescript에서 javascript 처럼 변수를 선언하면 자동으로 type 을 추론한다. 그리고 다른 형의 자료형을 대입하려고 하면 에러를 발생시킨다

    let a = "Hello";
    a = 1; // 에러 발생
    
  2. typescript 는 자동으로 type을 추론하지만, 명시적으로 type을 지정할 수도 있다.

    let a = "Hello"; //type 추론 (a : string)
    
    let b: boolean = true; //명시적 type 지정
    let b = true; // 결과는 같
    
  3. Array 를 만들 때, 보통 같은 type의 Array 를 만든다. 빈 Array 를 만들 때 type을 지정해줄 수 있다.

    let arr: number[] = [];
    arr.push("2"); //에러 발생
    
    let arr = []; // (arr : any[] 로 정의된다.)
    
  4. 객체에서 존재하지 않는 프로퍼티를 참조할 때, 에러를 발생시킨다.

    let dic = {
      a: "Hello",
    };
    
    dic.hello(); // hello() 라는 함수 호출 => 에러 발
    





# 2.2

optional type

let arr: {
  name: string;
  age: number;
} = {
  name: "GwanJin",
}; // 에러 발생 : age 프로퍼티가 없음.
let arr: {
  name: string;
  age?: number;
} = {
  name: "GwanJin",
}; // 에러 발생하지 않음 : ?: -> OPTIONAL TYPED

Alias

type Player = {
	name : string,
	age?: number
}

const playerA : Player = {
	name : "GwanJin"
};

const playerB : Player = {
	name : "Han"
	age : 22
}

Function Return

function playerMaker(name: string) {
  return {
    name,
  };
}

const Han = playerMaker("GwanJin");
Han.age = 12; // 에러 발생 playerMaker 가 반환한 객체에는 age 프로퍼티가 없음
type Player = {
  name: string;
  age?: number;
};

function playerMaker(name: string): Player {
  return {
    name,
  };
}

//const playerMaker = (name:string) : Player => ({name})

const Han = playerMaker("GwanJin");
Han.age = 12; //Player 로 반환 타입이 정의되었기에, 에러 발생하지 않음





# 2.3

readOnly (읽기 전용)

type Player = {
  readonly name: string;
  age?: number;
};
const playerMaker = (name: string): Player => ({ name });

const Han = playerMaker("GwanJin");
Han.name = "aa"; // 에러 발생
const numbers: readonly number[] = [1, 2, 3, 4];
numbers.push(1); // 에러 발생

Tuple

const player: [string, number, boolean] = ["gwanjin", 1, true];

const player: readonly [string, number, boolean] = ["gwanjin", 1, true]; // + readOnly

any : 아무 타입

undefined : 선언 x, 할당 x

null: 선언o, 할당 x

const a: any[] = [1, 2, 3, 4];
const b: any = true;

a + b; // 에러 발생하지 않는다.





# 2.4

  • unknown : 타입을 모를 때. 반드시 변수의 타입을 확인해야함

    function hello(a: unknown) {
      a.b(); // 에러 : Object is of type 'unknown'
    }
    
    function hello (a: unknown) {
      if (typeof a === "string") {
        ...
      } else if (type of a === "number) {
        ...
      }
    }
    
  • void : 값을 반환하지 않는 함수의 반환 값.

    function a() {
      console.log("Hello World");
    } // return 문이 없다 => 반환 값은 void 형식
    
  • never : 일부 함수 값을 반환하지 않는다. 에러를 throw 하거나 프로그램 실행을 종료함을 의미

    function fail(msg: string): never {
      throw new Error(msg);
    }
    





# 3.0

call signature 은 함수의 파라미터 타입과 리턴 타입을 정의한 것이다.

type Add = (a: number, b: number) => number; // Call Signature

const add: Add = (a, b) => a + b;





# 3.1

typescript가 아닌 통상의 function overloading은 같은 함수 이름을 가졌지만, 매개변수의 형식과 개수가 달라 함수끼리 구분되는 것을 말한다.

함수 오버로드

TypeScript 에서 Function Overloading 은 하나의 함수가 복수의 Call Signature을 가질 때 발생한다.

  1. 매개변수의 데이터 타입이 다른 경우, 예외 처리

    type Add = {
      (a: number, b: number): number;
      (a: number, b: string): number;
    };
    
    const add: Add = (a, b) => {
      if (typeof b === "string") return a;
      return a + b;
    };
    
  2. 매개변수의 개수가 다른 경우, 예외 처리

    type Add2 = {
      (a: number, b: number): number;
      (a: number, b: number, c: number): number;
    };
    
    const add2: Add2 = (a, b, c?: number) => {
      if (c) return a + b + c;
      return a + b;
    };
    

패키지나 라이브러리에서 많이 사용된다.



# 3.2

Polymorphism (다형성) 이란, 여러 타입을 받아들임으로써 여러 형태를 갖는 것을 말한다.

type SuperPrint = {
  (arr: T[]): T;
};

const superPrint: SuperPrint = (arr) => {
  return arr[0];
};

const a = superPrint([1, 2, 3]);
const b = superPrint([true, false, true]);
const c = superPrint(["a", "b"]);
const d = superPrint([1, 2, "a", "b", true]); // 에러가 나지 않는다.

⇒ 여기에서 generic type인 에 typescript가 추론한 타입 정보가 담긴다. (재사용 가능하다)

⇒ 자동으로 추론하기에 any 와는 다르다. (any는 추론하지 않음.)



# 3.3

제네릭은 C#이나 Java 같은 언어에서 재사용 가능한 컴포넌트를 만들기 위해 사용하는 기법.

단일 타입이 아닌 다양한 타입에서 작동할 수 있는 컴포넌트를 생성할 수 있.

Typescript 에서는 제네릭을 통해 인터페이스, 함수 등의 재사용성을 높일 수 있다.

function identity<Type>(arg: Type): Type {
  return arg;
}

// 제네릭 화살표 함수 (tsx기준)
const identity = <Type extends {}>(arg: Type): Type => {
  return arg;
};

let output = identity<string>("myString"); // 첫 번째 방법
let output = identity("myString"); // 두 번째 방법
// 두 번째 방법은 type argument inference(타입 인수 유추)를 사용합니다.
//즉, 컴파일러가 전달하는 인수 유형에 따라 자동으로 Type 값을 설정하기를 원합니다.



‘제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다.’




# 4.0

클래스 접근 제어자 (access modifier) 에 따른 참조 가능 범위

  선언한 클래스 내 서브 클래스 내 인스턴스
private
protected
public

추상(absract) 클래스 : 인스턴스를 생성할 수 없고, 다른 클래스가 상속만 받을 수 있는 클래스이다.

추상 메서드 : 추상 클래스에서 직접 정의되지 않으며, 서브 클래스(자식 클래스)에서 반드시 정의해야한다.

abstract class User{ // 추상 클래스
	constructor(
		private firstname:string,
		private lastname:string,
		public nickname:string
	){
	abstract getNickname():void // 추상 메서드
	}
}

class Player extends User{
	// 추상 메서드는 추상 클래스를 상속받는 클래스들이 반드시 구현(implement)해야하는 메서드이다.
	getNickname(){
		console.log(this.nickname)
	}
}

JS에는 없는 기능이다. 따라서 TS의 컴파일러는 해당 규칙들을 위반하면 에러를 내지만, 컴파일된 JS 코드에는 해당 기능들 명시적으로 구현되지는 않는다.



# 4.1

해시 테이블 (딕셔너리 내지는 오브젝트) 형태의 type 을 정의하는 법

type Words = {
  [key: string]: string; // 대괄호로 감싸준다.
};

클래스도 하나의 type으로서 : 뒤에 쓸 수 있다.

코드 챌린지. (class 로 CRUD 기능의 사전 구현)

type Words = {
  [word: string]: string;
};

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

class Dict {
  private words: Words;
  constructor() {
    this.words = {};
  }
  create(word: Word) {
    if (this.words[word.name] === undefined) {
      this.words[word.name] = word.def;
    }
  }
  read(name: string) {
    if (this.words[name] === undefined) {
      return "해당하는 단어가 없습니다.";
    } else {
      return this.words[name];
    }
  }
  update(word: Word) {
    if (this.words[word.name] !== undefined) {
      this.words[word.name] = word.def;
    }
  }
  del(name: string) {
    delete this.words[name];
  }
}

const red = new Word("red", "빨강");
const orange = new Word("orange", "주황");
const yellow = new Word("yellow", "노랑");

const englishDict = new Dict();
englishDict.create(red);
englishDict.create(orange);
englishDict.create(yellow);

console.log(englishDict.read("red"));





# 4.2

interface 는 object 의 type을 설명할 때만 사용한다. (type은 모든 것의 타입을 설명할 때 사용할 수 있다.)

또한 class 처럼 상속할 수 있다.

type name = string;

type typedPerson = {
  firstName: string;
  lastName: string;
};

interface Person {
  firstName: string;
  lastName: string;
}

interface GwanJin extends Person {
  gender: string;
}





# 4.3

implements (구현하다)

  • 클래스가 특정 인터페이스가 정의한 타입을 충족하는지 확인할 수 있다.

    1. 클래스를 이용한 코드 구현. (너무 길고, 자바스크립트 문법과 크게 다르지 않다)

      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}`;
        }
      }
      
    2. interface 와 implements 를 이용

      interface User {
        firstName: string;
        lastName: string;
        sayHi(name: string): string;
        fullName(): string;
      }
      
      class Player implements User {
        constructor(
          public firstName: string, //constructor 안에서는 반드시 public 을 써야함
          public lastName: string
        ) {}
        fullName() {
          return `${this.firstName} ${this.lastName}`;
        }
        sayHi(name: string) {
          return `Hello ${name}`;
        }
      }
      


      ⇒ 훨씬 더 코드가 짧아지고 직관적이다.


  • 클래스는 여러 인터페이스를 implements 할 수도 있다.
interface User {
  firstName: string;
  lastName: string;
  sayHi(name: string): string;
  fullName(): string;
}

interface Weapon {
  price: number;
}

class Player implements User, Weapon {
  // 여러 개의 interface 를 implements
  constructor(
    public firstName: string,
    public lastName: string,
    public price: number
  ) {}
  fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
  sayHi(name: string) {
    return `Hello ${name}`;
  }
}

Type Aliases과 Interfaces의 차이점

  • Type Aliases과 인터페이스는 매우 유사하며 많은 경우 자유롭게 선택할 수 있다. 인터페이스의 거의 모든 기능은 type에서 사용할 수 있으며, 주요 차이점은 type을 다시 열어 새 속성을 추가할 수 없는 것이다. 반면 인터페이스는 항상 확장 가능하다. ⇒ 무엇을 써야 하는가? 개인 취향. But, interface 조금 더 추천



# 4.5

“Polymorphism” (다형성)

generic + class + interface

interface SStorage<T> {
  [key: string]: T;
}

class LocalStorage<T> {
  private storage: SStorage<T> = {};
  //Create
  set(key: string, value: T) {
    if (this.storage[key] !== undefined) {
      return console.log(`${key}가 이미 존재합니다. update 호출 바랍니다.`);
    }
    this.storage[key] = value;
  }
  //Read
  get(key: string): T | void {
    if (this.storage[key] === undefined) {
      return console.log(`${key}가 존재하지 않습니다.`);
    }
    return this.storage[key];
  }
  //Update
  update(key: string, value: T) {
    if (this.storage[key] !== undefined) {
      this.storage[key] = value;
    } else {
      console.log(`${key}가 존재하지 않아 새로 만듭니다.`);
      this.storage[key] = value;
    }
  }
  //Delete
  remove(key: string) {
    if (this.storage[key] === undefined) {
      return console.log(`${key}가 존재하지 않습니다.`);
    }
    delete this.storage[key];
  }
  clear() {
    this.storage = {};
  }
}

const stringsStorage = new LocalStorage<string>();

const booleanStorage = new LocalStorage<boolean>();