본문 바로가기
TIL

타입스크립트 공부 내용 정리본 [기본] (노마드 코더 - 타입스크립트로 블록체인 만들기)

by 잼민타치 2024. 1. 31.

본 내용은 노마드 코더의 타입스크립트로 블록체인 만들기를 공부하면서 모르거나 헷갈리는 부분들을 정리한 내용입니다.

따라서 일부의 내용이 생략되어 있을 수 있으며, 필자의 개인적 생각들이 첨가되어 있을 수 있습니다.

 

-----------------------------------------------------------------------

 

 

 

 

자바스크립트는 에러가 생기면 런타임 당시에 에러가 발생한다 ⇒ 유저가 에러를 겪게 됨.

그러나 타입스크립트는 자바스크립트로 컴파일 될 때 에러가 발생한다 ⇒ 유저가 아닌 우리가 에러를 확인 가능하다.

 

let a = ‘hello’

 

라고 하고 나면 타입스크립트는 자동으로 a의 타입이 string임을 간주한다. 그래서 a=’bye’는 괜찮지만 a=1 하는 순간 에러가 발생.

아니면 let b : boolean = true 이런 식으로도 타입 체커를 통해서 설정이 가능하다. 그런데 이렇게는 안하는 걸 추천.

object의 타입도 설정 가능하다.

type Age = number;
type Name = string; <<< 이런식으로 alias를 줄 수 있음

type Player = {
	name: Name,
	age?: Age <<< 이렇게 하면 Player 오브젝트는 age를 가질 수도, 안 가질 수도 있다.
}

function playerMaker(name:string) : Player {
	return {
		name
	}
} <<< 이런 식으로 함수의 리턴 타입을 설정하거나, 매개변수의 타입을 설정 가능하다.
위의 함수와 똑같은 기능을 하는 화살표 함수를 만드려면

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

const nico = playerMaker("nico")
nico.age = 12

 

 

 

 

또한 타입스크립트는 readonly 기능도 있다.

const numbers: readonly number[] =  [1,2,3,4]
number.push(1) << 에러발생
맵이나 필터 같은 메소드는 쓸 수 있음.

 

 

 

 

또한 타입스크립트는 어레이가 여러 타입을 가질 수 있도록 하는 튜플 기능이 존재한다.

자바스크립트에서 let tupleLike = [1, 'string', true];

일 때 tupleLike은 숫자, 문자열, 불리언 값을 담고 있는 자바스크립트 언어로 튜플 처럼 보이지만 일반 언어 배열이다.

타입스크립트에서의 튜플은 고정된 수의 요소를 가지고 각 요소의 타입이 명시적으로 정해져 있는 배열의 한 형태이다.

cosnt player: [string, number, boolean] = ["nico", 1, true]

 

 

 

 

 

그리고 타입스크립트에서 자바스크립트처럼 쓰고 싶으면 any를 사용하면 된다.

const a : any[] = [1,2,3,4]
const b : any = true
a+b << 이런게 가능.

 

 

 

이제 자바스크립트에서는 불가하고, 타입스크립트에서만 가능한 기능들

 

 

1. unknown.

let a:unkown;

if(typeof a === 'number') {
	let b = a + 1
}
if(typeof a === 'string') {
	let b = a.toUppeerCase();
}

⇒ 만약 api로부터 어떤 타입의 값을 받게 될 지 모를 때 이렇게 쓸 수 있음. a 의 타입이 정해지지 않았다는 뜻.

이렇게 하면 타입스크립트로부터 보호를 받을 수 있음.

 

 

 

2. void

function hello(){
	console.log('x')
}

const a = hello();
a.toUpperCase() <<< 에러 발생

cpp에서와 마찬가지로 리턴값이 없는 함수의 타입. 지정해주지 않아도 되며

function hello() :void{

console.log(’x’)

}

라고 지정해주어도 되기는 한다.

 

 

3. never

주로 타입을 두가지로 받는 상황에서 발생.

예를 들어 매개 변수 a의 타입이 스트링이거나 넘버라고 해보자.

그러면 스트링일 때의 코드, 넘버일 때의 코드를 작성하고 나면 else에서 쓸 수 있는 타입은 없다. 그 때 else에서 a를 작성한 뒤 커서를 올려놓으면 never 라는 타입을 발견한다.

(사실상 거의 쓸 일 없으므로 그냥 이런 친구가 있다라는 느낌만 가져가자)


 

타입스크립트가 가지는 여러가지 특성들

 

 1. call signature

type Add = (a:number, b:number) ⇒ number(Add의 값이 넘버 형식이어야 한다는 말);

이런 식으로 Add 라는 타입을 만들어 놓으면

(위에서 작성한 것처럼 어떤 매개변수가 필요하고, 어떤 리턴 값이 나오는 지를 알려주는 걸 콜 시그니쳐라고 한다.

vsc에서는 Add에 마우스 커서를 갖다대면 뜬다.)

 

const add:Add = (a, b)⇒ a + b

가 에러가 뜨지 않는다.

 

 

 

 

 

 2. overloading

오버로딩은 함수가 서로 다른 여러개의 call signatures를 가지고 있을 때 나타날 수 있다.

 

type Config = {
	path: string,
	state: object
}

type Push = {
	(path:string):void <<< 첫번째 시그니처
	(config: Config):void <<< 두번째 시그니처
} <<< 이렇게 되면 Push라는 타입에서 path라는 string을 매개변수로 받을 수 도 있고, config라는 오브젝트를 매개변수로 받을 수 도 있는 것.

const push:Push = (config) => {
	if(typeof config === "string") console.log(config)
	else {
		console.log(config.path)
	}
}

(타입스크립트는 객체지향적 요소들이 많다. 즉 cpp에서 클래스 메소드를 위에서 선언하고 밑에서 구현하는 그 느낌과 유사하다.)

 

그러나 만약

type Add = {
	(a: number, b:number) :number,
	(a: number, b:number, c:number): number
}

이렇게 시그니처에서 매개변수의 개수가 다른 오버로딩이 발생하도록 구현해야 할 때도 있을 것이다.

이럴 때는 이 Add 타입의 인스턴스를 만들어줄 때,

 

 

 

const add:Add = (a, b, c?:number) => {
	if(c) return a+b+c
	return a + b
}

이런 식으로 c가 옵셔널이며 어떤 타입을 가지는지, 그 때 어떻게 되는지 구현할 수 있다. (그러나 이런 형태는 거의 못볼 것이다.)

 

 

 

 

 

 

 3. 다형성

 

type SuperPrint = {
	(arr: number[]):void
	(arr: boolean[]):void
}

const superPrint: SuperPrint = (arr) => {
	arr.forEach(i => consoel.log(i))
}

이렇게 SuperPrint라는 타입을 작성했다고 해보자.

그런데 나는 어레이가 꼭 넘버, 불리언 말고 다른 스트링도 받고 싶은 상황이다. 그러려면 어떻게 해야할까?

우리는 이를 위해 generic을 사용한다. 일종에 타입의 placeholder라고 생각하면 된다.

 

 

 

type SuperPrint = {
	<TypePlaceholder>(arr: TypePlaceholder[]):void
}

이렇게 작성하면 된다. 꺽쇠<>로 표현한 게 제네릭을 쓰겠다는 의미이고, 꺽쇠 안에 단어는 어떤 거든 상관 없다.

이렇게 제네릭을 만들어주면 알아서 타입스크립트가 시그니처를 만들어준다.

이 제네릭은 리턴 값에도 사용할 수 있는데,

type SuperPrint = {
	<TypePlaceholder>(arr: TypePlaceholder[]): TypePlaceholder
}

const superPrint: SuperPrint =(arr) => arr[0]

과 같이 작성하면 앞으로 superPrint([’hi’, 1,2]) 에서의 리턴 값은 ‘hi’인 string으로 될거고,

superPrint([1,3,4,’string’])에서의 리턴값은 1인 number가 될거다.

 

따라서 타입스크립트는 콜 시그니처, 오버로딩 이외에 

이렇게 superPrint 함수가 많은 형태(타입)을 가질 수 있는 것 처럼 다형성을 가지고 있다.

 

 

 

 

 

 

 

 

 


여기서 잠깐. 당연하게 드는 생각이 있지 않나?

 

굳이 제네릭을 만들어줘야 하나?
그냥 any라고 해버리면 되는거 아닌가?


제네릭을 만들게 되면 함수의 콜 시그니처를 만들기 때문에 그렇다.

그래서

type SuperPrint = <T>(a: T[]) => T
const superPrint: SuperPrint = (a) => a[0]

const d = superPrint([1, 2, true, false, "hello"])
d.toUpperCase()

이렇게 작성하게 되면 오류를 발생시키게 된다. 그러나, any라고 작성하면 컴파일 오류를 발생시키지 않으므로 문제가 된다.

위에 첫 두 줄은 다음과 같은 함수 코드로 대체 가능하다.

function superPrint<V>(a: V[]) {
	return a[0]
}

 

 

 

 

 

 

 

 

 


 4. 추상화 클래스 사용가능, 추상화 메소드 사용가능

 

(다음은 지피티에게 물어본 추상클래스에 대한 내용)

추상 클래스(abstract class)는 다음과 같은 특징을 가지고 있어요:

  • 추상 클래스 자체로는 인스턴스를 생성할 수 없어요. 즉, 직접적으로 new 키워드를 사용해 객체를 만들 수 없다는 뜻이에요.
  • 추상 클래스는 하나 이상의 추상 메소드(abstract method)를 포함할 수 있어요. 추상 메소드는 메소드의 선언만 있고, 구체적인 구현은 없는 메소드예요.
  • 추상 메소드를 포함하고 있는 클래스는 반드시 추상 클래스로 선언되어야 해요.
  • 추상 클래스를 상속받는 서브클래스는 반드시 추상 클래스에 정의된 모든 추상 메소드를 구현해야 해요.

하지만 반대로, 추상 클래스가 반드시 추상 메소드를 포함해야 하는 것은 아니에요. 추상 클래스는 추상 메소드 없이도 선언될 수 있어요. 이런 경우, 그 클래스는 단순히 인스턴스화 할 수 없는 일반 클래스처럼 작동해요. 이렇게 추상 클래스를 사용하는 이유는 여러 가지가 있을 수 있어요:

  1. 인스턴스 생성 방지: 특정 클래스가 직접 인스턴스화되는 것을 방지하고 싶을 때 사용할 수 있어요. 이는 해당 클래스가 일종의 '베이스 클래스'로만 사용되도록 강제하는 방법이에요.
  2. 의도 표현: 클래스가 '추상적인' 개념을 나타내고, 구체적인 구현은 서브클래스에 맡기고 싶을 때 사용될 수 있어요.

따라서 추상 클래스는 반드시 추상 메소드를 포함하지 않아도 되며, 추상 메소드가 없더라도 추상 클래스로 선언될 수 있어요. 이렇게 선언된 추상 클래스는 인스턴스화 할 수 없고, 주로 상속을 통해 사용되곤 해요.

 

 

 5. 클래스의 프라이빗, 프로텍티드, 퍼블릭 변수들 설정 가능

 자바스크립트에서도 가능한가?
대신 안되는데 오류가 뜨지는 않는건가?

⇒ 최근 기능으로 자바스크립트도 프라이빗 사용가능 하긴 하지만 그냥 어차피 타입스크립트 쓸 거잖아?

 

 

 

참고로 매개변수가 클래스의 인스턴스이길 원하면 add(word: Word) {}

이런 식으로 Word 클래스를 타입처럼 쓸 수 있게 된다. 이렇게 되면 word 매개변수는 Word 클래스의 인스턴스인 것.

보여주기만 하고 싶으면 public 뒤에 pbulic readonly term: string 이런 식으로 작성하면 된다. (자바스크립트에선 불가능한 기능) static도 가능

또한 타입스크립트에서는 type Team = string 같이 콘크리트하게 작성할 수도 있지만

type Team = "red" | "blue" | "green"

과 같이 타입이 특정한 값을 갖도록 설정도 가능하다.

 

 

 

 6. 인터페이스

타입스크립트에서 오브젝트 타입일 경우 타입 대신 인터페이스를 사용가능하다.

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

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

따라서 두 코드는 동일한 것이다. 또한 인터페이스는 조금 더 클래스 같은데(객체지향의 느낌이 난다),

 

 

interface User {
	name:string
}
interface Player extends User {
}

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

type User= {
	name:string
}
type Player = User & {
}

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

여기도 두 코드가 기능상 같지만 살짝 다르다. 그리고 interface는 같은 이름으로 여러개의 프로퍼티를 써놔도 나중에 합체시켜서 사용가능하지만 타입은 애초에 같은 이름의 타입을 만드는 순간부터 에러가 발생한다.

 

또한 인터페이스는 자바스크립트에 없는 문법이지만 extends를 통해 그 인터페이스를 상속받는게 있으면 추적 가능해진다. 따라서 extends 대신 implements 를 사용하면 크기를 줄일 수 있다.

 

 

대신에 단점이 존재하는데,

인터페이스를 상속할 때에는 property를 private으로 만들지 못한다. protected도 안되고, 상속 받은 친구는 무조건 public으로 작성해야 한다. 또한 클래스를 타입으로 쓸 수 있던 것처럼 인터페이스도 타입으로 쓸 수 있다.

타입스크립트 커뮤니티에선 클래스나 오브젝트의 모양을 정의하고 싶으면 인터페이스를, 그 외의 경우엔 타입을 쓰라고 한다.

그러므로 인터페이스와 타입은 추상 클래스를 대체할 수 있다고 본다. (셋 다 어떤 모양으로 정의되어야 하는 지를 알려주는 용도이므로)


타입스크립트로 작성할 때 자바스크립트의 버전이 몇인지를 작성해 줄 수가 있다. (tsconfig.json에서 compilerOptions에 target이 목표로 하는 버전인 것이다.) 그런데, 만약 클래스가 없는 자바스크립트 버전이 타겟인데, 제가 작성한 타입스크립트에는 클래스가 있는데 어떡하죠? ⇒ 그냥 알아서 클래스 안쓰고도 타입스크립트가 딱 만들어준다.

타입스크립트에게 어떤 api를 사용하고 어떤 환경에서 코드를 실행하는 지를 지정할 수가 있는데, (타겟 런타임 환경이 무엇인지를 지정하는 것) 프로그램이 브라우저에서 실행되면 Lib에 “DOM” 유형 정의를 할 수가 있다.

타입스크립트는 localStorage, Math, window의 타이을 이해하고 인지하고 있다. 근데 그걸 어떻게 알고 있을까? 누군가가 시간을 들여서 타입스크립트에게 localSotrage의 구조, 매개변수, 리턴 값과 리턴 타입을 설명해주었기 때문이다. 이것이 타입 정의다.

그런데 본질적으로, 타입 정의를 왜 해줘야 해요? : 라이브러리나 프레임 워크는 자바스크립트로 짜여져 있어. 이러한 라이브러리를 타입스크립트 프로젝트에 쓰려면 그것들의 타입에 대해 알 수가 없거든.

그러므로 .d.ts 파일에서 타입 정의를 해주면 된다. (호출 시그니처를 작성해주자.) 구현을 하는 것이 아니고, 일종의 cpp에서의 header 파일처럼 선언만 하는 느낌


위에서 한 행위는 자바스크립트 패키지를 타입스크립트 프로젝트에 설치하기 위한 것이었다.

그렇다면 자바스크립트에서 타입스크립트로 이전하는 경우에는 어떨까?

모든 줄을 삭제하고 타입스크립트로 변경하기는 현실적으로 어렵다.

그렇기에

// @ts-check
/**
* Initializes the project
* @param {object} config
* @returns boolean
*/

export function init(config) {
	return true;
}

와 같이 코멘트를 작성하면 자바스크립트로 작성한 파일이라도 타입스크립트의 보호를 받을 수가 있다.

이렇게 JSDoc을 통해서 JS에게 타입을 제공할 수 있다.


ts-node를 깔아주면 빌드 없이 타입스크립트를 실행할 수 있게 된다.

nodemon은 src 내에 파일이 바뀌면 알아서 다시 시작.

(pacakage.json에 있는 tsc는 타입스크립트 코드를 자바스크립트로 컴파일하라는 뜻이다. 따라서 tsconfig.json은 타입스크립트 컴파일러 설정을 담고 있는 파일이다. include 안에 있는 파일을 컴파일 하라는 것.)

esModuleInterop

CommonJS 모듈을 ES6 모듈 코드베이스로 가져오려고 할 때 발생하는 문제를 해결합니다. ES6 모듈 사양을 준수하여 CommonJS 모듈을 정상적으로 가져올 수 있게 해줍니다.

또 최신 노드 모듈을 사용하면 그 안에 @types\node가 포함되어 있어서 원래라면 자바스크립트로 작성된 패키지들이 .d.ts 가 자동으로 포함되어 깔리고, 이는 곧 타입스크립트가 인식할 수 있음을 의미한다.