[TypeScript] 노마드 Typescript로 블록체인 만들기 1

먼지·2022년 8월 12일
0

TypeScript

목록 보기
2/3
post-thumbnail

Typescript로 블록체인 만들기

2. OVERVIEW OF TYPESCRIPT

Why not JavaScript

왜 JS를 버리고 타입스크립트로 넘어올까?

"안정성" 더 정확히 말해서 타입 안정성덕분에

  • 타입스크립트의 장점을 이용할 수 있음.
  • 엄청난 개발자 경험을 누릴 수 있음
  • 코드에 버그가 엄청 줄어들게 됨
  • 런타임 에러가 줄고 생산성이 늘어남
    타입 안정성은 타입스크립트 제공하는 가장 큰 장점

런타임 에러가 뭐고 왜 최악일까?

자바스크립트는 매우 유연한 언어로 멍청하고 이상한 코드를 작성해도 개발자를 이해하려고 도와줌. 에러를 보여주지 않으려고 많은 노력을 함.

[1, 2, 3, 4] + false // '1,2,3,4false'

자바스크립트는 다른 언어들과 달리 이런 말도 안 되는 유효하지 않은 코드를 작성하는 것을 허용하고, 에러 메세지도 보여주지 않음. 이게 자바스크립트의 단점 중 하나로 js는 개발자에게 뭔가 잘못되었다는 걸 알려주는 게 좋을 거임

아쉽게도 현재 자바스크립트는 그걸 지원하지 않음.. 하지만 이번엔 실제로 일어날 수 있는 예를 들어보자.

function divide(a, b) {
  return a / b;
}
divide(2, 3); // 0.6666666666666666

// 자바스크립트는 이 함수를 올바르게 사용하도록 강제하지 않음
divide('xx'); // NaN

자바스크립트는 a와 b가 뭔지 전혀 모름. 그리고 필수 입력값인지 선택사항인지 전혀 알고 있지 않음 그저 실행할 뿐.. 우린 Java, C#, Go 등 다른 언어처럼 좀 더 보호받을 필요가 있음!

하지만 최악의 에러는 런타임 에러. 런타임 에러는 콘솔 안에서 일어나는 에러임
이런 에러는 유저의 컨퓨터에서 코드가 실행될 때만 일어나는 에러일 수도 있음

이건 코드를 실행하기 전에 최소화시킬 수 있는 에러 중 하나

const haru = { name: 'haru' }
haru.hello() // TypeError

이상적으론 코드가 실행되기 전에 언어가 자체적으로 haru 객체에는 hello method가 없다는 걸 알려주는 게 좋음. 코드가 실행되기 전에. 이 에러는 유저의 컴퓨터에서 코드가 실행되면 나타나는 에러로 결코 좋은 게 아님.. 좋은 프로그래밍 언어라면 객체를 분석해서 함수가 없다는 걸 개발자에게 알려줄 거임.

예를 들어 Rust나 Go에서는 이런 식으로 코드를 작성하면 코드가 실행조차 안 됨. 컴파일이 실패해서 시작조차 되지 않음. 이 에러를 고치고 나서야 유저에게 코드를 보낼 수 있다고 알려줄 것임. 하지만 자바스크립트 경우 유저에게 전달하면 유저가 코드를 실행했을 때야 비로소 에러가 있다는 것을 알려줌

이상적으론 코드가 실행되기 전에 실수를 바로잡아야 됨. 이 때문에 개발자들이 타입스크립트로 넘어오는 것!

How Typescript Works

타입스크립트는 strongly typed(강타입) 프로그래밍 언어
타입스크립트의 경우 작성한 코드가 어셈블리 코드나 바이트 코드처럼 기계가 실행할 수 있는 다른 종류의 코드로 변환되는 것이 아닌, 일반적인 자바스크립트로 변환됨
변환하는 이유는, 브라우저가 타입스크립트가 아니라 자바스크립트를 이해하기 때문
Node.js는 타입스크립트 자바스크립트 양 쪽 다 이해할 수 있음.

타입스크립트가 제공하는 보호장치는 타입스크립트 코드가 자바스크립트로 변환되기 전에 발생함. 즉 타입스크립트가 먼저 우리의 코드를 확인하고 변환된 자바크립트 안에서 바보 같은 실수가 일어나지 않게 확인을 해줌. 일단 타입스크립트 코드를 작성해서 그 코드를 컴파일하면, 보호장치 없는 자바스크립트 코드가 됨. 하지만 타스 코드에 에러가 있으면 그 코드는 자바스크립트 코드로 컴파일되지 않음. 에러가 발생할 것 같은 코드를 감지하면 자바스크립트로 아예 컴파일되지 않음

이런 보호장치가 유저가 코드를 실행하는 런타임에 발생하는 게 아님. 성공적으로 컴파일된 자바스크립트 코드를 유저가 사용하게 되는 것

자바스크립트는 이런 코드를 실행해도 에러가 없었지만, 타입스크립트에서는 숫자 배열에 boolean을 더할 수 없다는 에러를 표시해 줌

우린 이게 말도 안 된다는 걸 알았지만 자바스크립트는 몰랐음. 자바스크립트는 이런 코드들이 실행되게 두었지만, 타입스크립트는 컴파일조차 안 됨

코드를 많이 바꾸지 않아도 되기 때문에 사람들이 타입스크립트를 좋아하는 이유. 이런 보호장치가 생기는 이유는 타입 추론 때문임!

⭐ Typescript
└ Strongly typed programming
└ 컴파일 시 javascript로 변환됨
└ 에러가 발생 → 컴파일 X

Implicit Types vs Explicit Types

TypeScript의 Type 시스템은 두 가지 접근 방식을 결합했음. 데이터와 변수의 타입을 명시적이거나 변수만 생성하는 식으로 정의함. 좋은 점은 타입을 추론해준다는 것

a가 string type 이라는 것을 알게 됨. 이건 자바스크립트 코드와 별반 다르지 않은 하나의 옵션으로 타입을 추론하게 하는 것

하나는 타입스크립트에게 구체적 명시적(변수 b가 boolean이어야 한다)으로 말해주는 것. 이게 진짜 Typescript가 만든 문법으로, 우리가 Type Checker와 소통하는 방식임.

둘 다 결과는 같지만 타입 추론보다는 구체적으로 타입을 명시하는 것이 좋음.

하지만 어떤 때는 배열 안에 아무것도 들어있지 않을 수도 있음. 이럴 때는 타입스크립트에게 이것이 어떤 자료형의 array 인지 타입을 명시해 줘야 함

// nooo
const array = [];
array.push('1');

// yess
const array: number[] = [];
array.push(1);

보통은 명시적 표현은 최소한으로 사용하고 타입 추론을 이용하는 것이 타이핑 시간을 줄일 수 있어서 더 나을 수 있음

요약

⭐ Type 시스템
└ 명시적 정의(변수 선언 시 타입 정의)
let a: boolean = "x"
→ 🚫 boolean 타입에 string타입 할당 불가 알림
└ 변수만 생성(타입 추론)
let b = "hello"
→ b가 string 타입이라고 추론
b = 1
→ 🚫 string 타입에 number타입 할당 불가 알림

Types of TS part One

타입스크립트 문법은 그냥 콜론(:)을 써주고 타입을 적으면 됨. 변수를 생성할 때 등 어떤 때는 Typescript가 자동으로 타입을 추론하게 하는 것이 좋음. 가능한 한 추론하게 하기

기본적인 Typescript의 타입 외의 다른 타입 알아보기!

Optional

object type은 쓸모없음

player object에서 몇몇은 age, name 프로퍼티가 있고 몇몇은 없게 만들고 싶다면 어떻게 이것을 Typescipt에서 쓸 수 있을까? 어떻게 소통해야 할까?

optional parameter(선택적 변수)를 지정하는 방법

// object의 타입을 정의
const player : {
	name: string,
	age: number
} = {
	name: 'nana',
	age: 20
}

// age를 선택적이게 만들기
const player : {
	name: string,
	age?: number // 물음표만 추가!
} = {
	// 물음표가 없으면 오류가 생겼을 것임
	name: 'nana'
}

Typescript는 player.age가 undefined 일 수도 있다고 말해줌

보호장치를 추가

하지만 player가 많아지면 같은 작업을 반복해야 함

const player1 : {
	name: string,
	age?: number
} = {
	name: 'hana'
}

const player : {
	name: string,
	age?: number
} = {
	name: 'duna'
}

...

Alias

Alias(별칭) 타입을 생성하면 반복 작업을 하지 않아도 돼서 더 적은 코드를 쓸 수 있음

// 첫 글자는 대문자로!
type Player = {
	name: string,
	age?: number
}

// 이제 player를 생성할 때 Player 타입을 적용
const player: Player = {
	name: 'sena'
}

코드의 재사용성을 높이기 위해 예를 들어 age의 타입에 대한 다른 Alias를 만들 수도 있음. 이건 object에만 유효한 게 아닌 모든 타입에 적용이 가능! 물론 이런 식으로 과한 재사용이 아닌 코드가 깔끔하고 명확해질 때까지만

type Age = number;

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

Function return type

함수가 return 하는 타입이 무엇인지 알면 더 멋진 자동완성과 더 많은 보호장치를 줄 것임

이렇게만 작성하면 TS는 우리가 object를 return 하는 것만 알고 있음

function playerMaker (name: string) { // 인수의 타입을 지정
	return {
		name // = name: name
	}
}

Typescript에게 playerMaker는 Player 타입을 return 하고 있다고 말하기

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

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

cosnt hana = playerMaker('hana')
// hana.age

// 화살표 함수도 같은 문법을 사용. 콜론, 그리고 타입
const arrowPlayerMaker = (name: string): Player = ({ name })

Types of TS part Two

readonly

readonly는 요소들을 읽기 전용으로 만들 수 있음. 자바스크립트엔 없는 타입스크립트 보호장치로 Player의 name을 수정하려 시도하면 타입스크립트가 멈춰줌

const numbers: readonly number[] = [1, 2, 3, 4]
numbers.push(1) // error

const names: readonly string[] = ['hana', 'duna']
names.push('sena') // error
names.map(name => ...) // map은 가능! 왜냐면 array를 변경하지 않기 때문

Typescript의 보호장치로 immutability(불변성) 을 가짐

Tuple

array를 생성할 수 있게 하는데, 최소한의 길이를 가져야 하고 특정 위치에 특정 타입이 있어야 함

이름, 나이, 챔피언인지의 여부를 가지는 한 array를 만든다고 가정했을 때, 요소의 첫 번째 인덱스의 요소는 항상 string, 두 번째는 number, 세 번째는 boolean 이어야 함. 이걸 Typescript의 방식으로 만들려면?

const player: [string, number, boolean] = ['hana', 20, true]
player[0] = 1 // error

이걸 사용하면 항상 정해진 개수의 요소를 가져야 하는 array를 지정할 수 있다는 것을 기억! 또한 원하는 순서에 맞는 타입을 가져야 함. 또 원한다면 Tuple과 readonly를 합칠 수 있음

const player: readonly [string, number, boolean] = ['hana', 20, true]
player[0] = 'duna' // error

이건 Typescript에만 있음. 자바스크립트에선 이렇게 보임

코드를 저장하고 실제 production 환경으로 push하기 전에 오류를 확인할 수 있음

undefined, null, any

알아야 할 다른 타입들은 자바스크립트 또한 가지고 있는 type

let a: undefined = undefined
let b: null = null

비어있는 값들을 쓰면 기본값이 any가 되는데, any는 Typescript로부터 빠져나오고 싶을 때 쓰는 타입으로 말 그대로 아무 타입이나 될 수 있음. any 사용을 막기위해 추가할 수 있는 몇 가지 규칙이 있지만 그럼에도 불구하고 any는 Typescript에 존재하고 우린 그것을 사용하는 방법을 배울 필요가 있음

// typescript는 기본적으로 a를 any의 array라고 생각함
let a = []
let b: any = 1
b = 'hi'
b = true

any는 되도록 사용하지 않는 것이 좋음. 타입스크립트의 보호장치로부터 빠져나와 말도 안 되는 코드가 허용되는 자바스크립트에 있는 것이 됨

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

a + b // 이렇게 해도 작동함

하지만 가끔은 any를 써야 할 때가 있음..! 신중하게 사용 😱

Types of TS Three

Typescript에만 존재하는 매우 독특하고 멋진 타입 몇 가지를 살펴보자. 핵심은 Typescript에서 중요한 포인트는 Type Checker와 소통하는 것!

unknown

변수의 타입을 미리 알지 못할 때 사용. 만약 API로부터 응답을 받는데 그 응답의 타입을 모르면 unknown이라는 타입을 쓸 수 있음. 이렇게 하면 TS로부터 일종의 보호를 받게 됨. 어떤 작업을 하려면 이 변수의 타입을 먼저 확인해야 하는 방식으로 작성

let a: unknown
let b 
// b = a + 1 허용 x

// 먼저 타입을 확인하는 코드를 작성하면 작업을 허용해줌
if(typeof a === 'number') {
	// 이 범위 안에서는 a가 number이기 때문
	b = a + 1
} else if(typeof a === 'string') {
	b = a.toUpperCase()
}

void

아무것도 return 하지 않는 함수를 대상으로 사용하며, 타입스크립트가 자동으로 인식하기 때문에 보통 따로 지정해 줄 필요는 없음

// 원하면 써도 되지만 필요하진 않음
function hello(): void {
	console.log('hello')
}

const a = hello();
// a.toUpperCase() // Property 'toUpperCase' does not exist on type 'void'.

never

거의 사용하지 않는 타입으로 함수가 절대 return하지 않을 때 발생함. 예를 들어 함수에서 exception(예외)이 발생할 때

// return하지 않고 오류를 발생시키는 함수
function hello(): never {
	// return 'X' // error
	throw new Error('xxx') // 정상 작동
}

또한 never는 타입이 두 가지 일 수도 있는 상황에 발생할 수 있음. if 안에서는 string형의 name 반환 else if 안에서는 number형의 name 반환 else 안에서는 never형의 name 반환
⇒ 즉, 제대로 인자가 전달되었다면 else로 올 수 없음

function hello(name: string | number): never {
	if(typeof name === 'string') {
		// something
		name // name에 마우스를 올려보면 string
	} else if(typeof name === 'number') {
		name // number
	} else {
		name // never
	}
}

3. FUNCTIONS

Call Signatures

우리가 할 것은 함수에 a, b 타입들을 작성하지 않는 것. Plyaer type처럼 add 함수만의 타입을 만들고 싶다면?

function add(a: number, b: number) {
	return a + b;
}

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

call signatures라는 것은 함수 위에 마우스를 올렸을 때 보게 되는 걸 말함. 위의 add 함수의 경우엔 const add: (a: number, b: number) => number 이게 이 함수의 call signature로, 이건 우리가 함수를 어떻게 호출해야 하는 것인지와 함수의 반환 타입을 알려줌

// 타입을 만들어 함수가 어떻게 작동하는지 서술
type Add = (a: number, b: number) => number;

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

프로그램을 디자인하면서 타입을 먼저 생각하고 그리고 나서 코드를 구현. React에서 함수로 props를 보낼 때, 어떻게 작동할지 미리 설계 가능함

Overloading

실제로 많은 오버로딩된 함수를 직접 작성하지 않고 대부분의 시간을 다른 사람들이 만든 외부 라이브러리를 사용할 텐데 이런 패키지나 라이브러리들은 오버로딩을 엄청 많이 사용하기 때문에 오버 로딩이 어떤 것인지 아는 것은 중요함

밑에 코드는 all signature를 길게 작성하는 방법으로, 이런 방법이 존재하는 이유는 오버로딩 때문. 오버로딩은 함수가 여러 개의 call signatures를 가지고 있을 때 발생시킴. 그냥 여러 개가 아니라 서로 다른 여러 개의 call signature 를 가졌을 때

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

바보같은 예시지만 오버로딩의 핵심을 볼 수 있음.

type Add = {
	// Add 함수는 이 모양으로 부를 수도 있고
	(a: number, b: number): number
    // 이 모양으로 부를 수도 있음
    (a: number, b: string): number
    // 그 즉시 ts는 이게 잘못되었다는 걸 알게 됨
}

// b가 string도 될 수 있고 number또 될 수 있어서 string과 number는 더할 수 없다
// const add: Add = (a, b) => a + b; // error

// 그래서 먼저 확인
const add: Add = (a, b) => {
  if(typeof b === 'string') return a
  return a + b
}

다시 말하면, 오버로딩은 여러 call signatures가 있는 함수일 뿐임!

실제로 우리가 겪을만한 예시. 이게 완벽한 오버로딩의 예시로 두 가지 모두 잘 작동하는 Nextjs에서 사용하는 실제 예시임. string으로 보낼 수도 있고, object로도 보낼 수 있음

// Nextjs에서는 라우터를 가지고 있는데, 페이지를 변경할 수 있음
Router.push('/home'); // string값으로 home을 주면 home 페이지로 이동시킴

// 또한 객체 형식으로 보내줄 수도 있고 추가적으로 뭔가를 더 넣어서 같이 보낼 수도 있음
Router.push({
	path: '/home',
  	state: 1
});

그래서 어떻게 해결? type을 지정. 이건 패키지나 라이브러리를 디자인할 때 많이 사용함. 핵심은 string이나 Config 타입을 가지고 있다면 타입스크립트는 내부에서 그 타입을 체크하게 해줌

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

type Push = {
	(path: string): void
    (config: Config): void
}
    
const push: Push = (config) => {
	if(typeof config === 'string') // something
    else {
    	console.log(config.path)
    }
}

하나의 call signature 는 두 개의 파라미터를 가지고 다른 하나는 6개의 파라미터를 가지는 경우가 발생할 수도 있음. 다른 여러 개의 argument를 가지고 있을 때 발생하는 효과

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

// 다른 개수의 파라미터를 가져서 타스가 불평
// const add: Add = (a, b, c) => 

다른 개수의 파라미터를 가지게 되면, 나머지 파라미터도 타입을 지정해줘야 함. Add를 부를 때 a와 b를 부를 수도 있고 또는 a, b, c를 부를 수도 있어서 기본적으로 c는 옵션이라는 거를 알려주기. 자주 보는 경우는 아님

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

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

add(1, 2)
add(1, 2, 3)

// 이렇게는 안 되나..?
// type Add = (a: number, b: number, c?: number) => number

Polymorphism

다형성 generic 은 뭘까? 그리스어로 poly는 다수의 란 듯이고 여러 개의 면을 가지고 이씀. 그런 형태가 polygon(다각형)으로 morphos or morphic은 형태나 구조 혹은 모양이란 뜻. 이것들을 조합하면 many(poly) structure(morphous)가 됨. polymorphous란, 여러가지 다른 구조들 여러가지 다른 모양둘이나 형태들

기본적으로 함수는 여러가지 다른 모양 형태를 가지고 있음.

// call signature
type SuperPrint = {
	(arr: number[]): void
	(arr: boolean[]): void
	(arr: string[]): void
}
    
// 배열을 받고, 그 배열의 요소를 하나씩 print해주는 함수
function superPrint: SuperPring(arr) {
	arr.forEach(i => console.log(i))
}

superPring([1, 2, 3, 4])
superPring([true, false, true])
superPrint(['a', 'b', 'c']) // 동작 x

어떤 타입의 배열을 받아 void를 리턴하는 함수를 또 넣어주지 않고, 다형성을 활용하는 더 좋은 방법이 있음. number, string, boolean, void, unknown 는 concrete type임. 우리는 타입스크립트한테 generic 타입을 받을 거라고 알려줄 건데, generic이란 타입의 placeholder 같은 거임

그게 뭔지 추론해서 함수를 사용하는 것임. 우리는 call signature가 3개나 있는데 만약 [1, 2, true, false] 라는 배열을 주고 싶으면? 말은 안 되지만 우리가 하고 싶은 건 어떤 타입의 배열이든 superPrint가 잘 작동하는 거임 또 SuperPrint type에 (arr: (number | boolean)): void 를 추가할 수는 있지만 그 대신 우리는 generic을 사용할 거임

타입스크립트가 타입을 유추하는 것으로, 확실한 타입을 모를 때 사용함. 우리가 코드를 작성하고 함수를 구현하고 사용할 때는 물론 concrete type을 사용해야 함. 하지만 call signature를 작성하는데 concrete type을 알 수 없을 때도 있음 그런 경우 사용!

// TS에게 generic을 사용하고 싶다고 알려주기
type SuperPrint = {
	// 꺾쇠괄호 안에 원하는 제네릭 이름을 넣을 수 있음.
    // 보통은 알파벳 T나 V를 많이 씀
    // 이것이 TS에게 이 call signature가 제네릭을 받는다는 걸 알려주는 방법
	<TypePlaceholder>(arr: TypePlaceholder[]): void
}

function superPrint: SuperPring(arr) {
	arr.forEach(i => console.log(i))
}

// 모두 해결!
// 이 라인에선 superPrint가 number 타입의 배열로 동작한다는 걸 알게 됨
superPring([1, 2, 3, 4]) 
// 이 라인의 코드에선 TS는 superPrint 함수에 boolean 타입이 주어졌다는 걸
superPring([true, false, true])
superPrint(['a', 'b', 'c'])
superPrint([1, 2, true, 'hi'])
👆 superPrint: <string | number | boolean>(arr: (string | number | boolean)[]) => void

타입스크립트는 이 값들을 보고 타입을 유추하고 그 유추한 타입으로 call signature을 보여줌. 보다시피, 타입스크립트는 placeholder에서 알아낸 타입으로 대체해 줌

요약

✅ polymorphism(다형성)
❓poly란?

  • many, serveral, much, multi 등과 같은 뜻
    ❓morphos란?
  • form, structure 등과 같은 뜻
    ❗polymorphos = poly + morphos = 여러 다른 구조

concrete type

  • number, boolean, void 등 지금까지 배운 타입

generic type

  • 타입의 placeholder

─ 예시 ────────────────────────
type SuperPrint = { (arr: T[]): void }
type SuperReturn = { (arr: T[]): T }

const superPrint: SuperPrint = (arr) => {
arr.forEach(i => console.log(i))
}
const superReturn: SuperReturn = (arr) => arr[0]

superPrint([1, 2, false, true])
console.log(superReturn([1, 2, 3, 4]))
────────────────────────────

Generics Recap

이전엔 call signature를 하나하나 작성하는 것부터 시작해서 generic type을 사용하는 것까지 여러 타입의 배열을 가지는 함수를 만드는 걸 시도해 봤음. placeholder는 call signature 를 요구하는 대로 생성함. (마우스를 올리면 call signature를 볼 수 있음)

만약 함수의 목표가 아무거나 넣어도 아무거나 리턴이 된다면 any를 넣는 게 더 나을 수도 있다 생각할 수 있지만 이건 우릴 더이상 지켜주지 않음.

type SuperPrint = (a: any[]) => any

const superPrint: SuperPrint = a => a[0]

// a, b, c, d 모두 any
const a = superPrint([1, 2, 3, 4])
const b = superPrint([true, false, true])
const c = superPrint(['a', 'b', 'c'])
const d = superPrint([1, 2, true, 'hell'])
d.upperCase() // number이기 때문에 error

any -> generic 제네릭 = 우리가 요구한 대로 signature 를 생성해줄 수 있는 도구

type SuperPrint = <T>(a: T[]) => T
const superPrint: SuperPrint = a => a[0]
const d = superPrint([1, 2, true, 'hell'])
d.upperCase() // error  => string | number | boolean

superPrint 타입 제네릭을 하나 더 추가하고 싶다면

// TS는 generic이 처음 사용되는 지점을 기반으로 이 타입이 무엇인지 알게 됨
// 제네릭을 처음 인식했을 때와 제네릭의 순서를 기반으로 제네릭의 타입을 알게 됨
type SuperPrint = <T, V>(a: T[], b: V) => T

const superPrint: SuperPrint = a => a[0]

// a, b, c, d 모두 any
const a = superPrint([1, 2, 3, 4], 'x')

any와 같은 작업이 아닌 "우리가 하는 요청에 따라" call signature를 생성한다는 뜻으로 매우 다름.

댓펌

그렇다면 그냥 any를 넣는 것과 Generic의 차이는 무엇일까?

type SuperPrint = {
(arr: any[]): any
}

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

let a = superPrint([1, "b", true]);
// pass
a.toUpperCase();

any를 사용하면 위와 같은 경우에도 에러가 발생하지 않는다

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

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

let a = superPrint([1, "b", true]);
// error
a.toUpperCase();

Generic의 경우 에러가 발생해 보호받을 수 있다

  • Call Signature를 concrete type으로 하나씩 추가하는 형태이기 때문!

type SuperPrint = {
(arr: T[], x: M): T
}

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

let a = superPrint([1, "b", true], "hi");

위와 같이 복수의 Generic을 선언해 사용할 수 있다


Generic : 변수나 인수에 타입을 정해주는 Concrete같이 딱딱한 기법과 달리 어떤 타입을 쓸지 정해주지 않고 그 타입에 대해 어떤 변수를 넣어주냐에 따라 결정되는 유연한 기법이다.
any와 가장 큰 차이는 타스의 타입 체커로부터 보호를 못받는다는 것이다

적용방법

type = {a : T} : T


Generics

제네릭은 C#이나 Java와 같은 언어에서 재사용 가능한 컴포넌트를 만들기 위해 사용하는 기법입니다. 단일 타입이 아닌 다양한 타입에서 작동할 수 있는 컴포넌트를 생성할 수 있습니다.
(구체적인 타입을 지정하지 않고 다양한 인수와 리턴 값에 대한 타입을 처리할 수 있다.)
타입스크립트에서 제네릭을 통해 인터페이스, 함수 등의 재사용성을 높일 수 있습니다.

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 값을 설정하기를 원합니다.

https://www.typescriptlang.org/docs/handbook/2/generics.html

Conclusions

우리는 제네릭을 사용해서 위와 같은 call signature를 직접 만들 일은 거의 없을 거임. 라이브러리를 만들거나, 다른 개발자가 사용할 기능을 개발하는 경우엔 제네릭이 유용하지만 우리가 직접 작성할 일은 많이 없고 이 모든 것을 일반 함수로 대체할 수 있음

// 우리가 만들었던 거랑 똑같음
funtion superPrint<T>(a: T[]) {
	return a[0]
}

// const a = superPrint<boolean>([1, 2, 3, 4]) // error
// -> 구체적으로 작성해도 되지만, 그냥 TS가 유추하게 두는 것이 좋음
const a = superPrint([1, 2, 3, 4])
const b = superPrint([true, false, true])

제네릭을 사용해 타입을 생성할 수도 있고 어떤 경우는 타입을 확장할 수 있음 또는 코드를 저장하기도 함. type들 끼리 일종의 상속을 할 수 있음 굳이 따지자면 재사용. 많은 것들이 있는 큰 타입을 하나 가지고 있는데 그중 하나가 달라질 수 있는 타입이라면 거기에 제네릭을 넣으면 됨

// Extra, Lalala 등 대문자로 시작하는 모든 문자를 넣을 수 있음
type Player<E> = {
	name: string
  	extraInfo: E
}
type NicoExtra = {
	favFood: string
}
// 타입을 또 다른 타입에 넣어서 사용 가능
type NicoPlayer = Player<NicoExtra>

// const nico: Player<{favFood: string}> = ({
const nico: NicoPlayer = ({
  	name: 'nico',
  	extraInfo: {
    	favFood: 'kimchi'
    }
})
const v: Player<null> = {
	name: 'v',
  	extraInfo: null
}

또 제네릭은 함수에서만 쓰이는 게 아니라 정말 많은 곳에서 쓰임. 또 Array 타입의 인터페이스를 확인해 보면 타입스크립트 표준 라이브러리에 Array가 Interface로 되어있고, 핵심은 Array를 생성하는데 제네릭을 받고 있는 것

// 이것 또한 제네릭을 사용하는 또 다른 방법으로, 이렇게 생긴 걸 많이 보게 될 거임
type A = Array<number>

let a: A = [1, 2, 3, 4]

만약 React.js를 Typescript와 같이 사용할 때 useState 함수는 제네릭을 받음

const [num, setNum] = useState<number>(0)
profile
꾸준히 자유롭게 즐겁게

0개의 댓글