Typescript - call signatures, overloading, polymorphism, generic

Angela_Hong·2023년 8월 2일

TypeScript

목록 보기
2/6
post-thumbnail

call signatures, polymorphism, overloading, generic

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

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

Call Signatures

내가 작성한 함수만의 타입을 만들고 싶을때

a: number, b: number의 number 타입을 작성하고 싶지 않고, 내가 작성한 add함수만의 타입을 만들고 싶을 때
User라는 타입과 Age라는 타입도 만들었던 것과 같이 함수에 대한 타입도 만들 수 있다
앞으로 작성할건 call signatures가 될 것이다

call signatures란?
ts 안내창 > 함수 위에 마우스를 올렸을 때 보게 되는 걸 말한다
예) const add: (a: number, b: number) => number 이걸 call signatures라고 한다
함수를 어떻게 호출해야하는 것인지 알려주고 인자의 타입과 함수의 반환 타입을 알려준다

// 함수의 call signatures를 만드는 방법
type Add = (a: number, b:number) => number;

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

overloading

overloading = function overloading = method overloading 이라고도 부른다
우리가 사용하는 라이브러리 같은 곳에서 overloading을 많이 사용하기 때문에 알아야한다
overloading이란?
함수가 여러개 즉, 서로 다른 여러 개의 call signatures를 가지고 있을 때 발생시킨다
다시 말하면, overloading은 여러 call signatures가 있는 함수라고 말할 수 있다

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;  // b가 number이면, a+b를 리턴한다
}

⭐️⭐️⭐️⭐️여기 나한테 좀 어려움⭐️⭐️⭐️⭐️

예를 들어, Next.js의 router를 쓸때

Router.push({
	path: "/home",
    state: 1
})

Router.push("/home")
// 위의 object 또는 string으로 전달을 할 수 있다
// 이런 경우에 overloading을 어떻게 사용할 수 있을까?
// 일상생활에서 개발할 때 볼 수 있는 overloading
type Config = {
	path: string,
  	state: object
}

type Push = {
  	// 이건 Router.push("/home")의 string을 의미
	(path:string):void	 // void는 아무것도 리턴하지 않는 것을 의미
  	// 이건 Router.push({path: "/home", state: 1})의 string과 object를 의미
  	(config: Config):void  // obj는 config obj이므로 위에 Config type을 만들어서 진행
}

const push: Push = (config) =>{ 	// 여기서 path나 config타입을 받을텐데, 이런 경우에는 config를 받고 아무것도 리턴하지 않는다.  // 그리고 (config)에 마우스를 가져다 대면 string | Config를 받을 수 있다고 나온다
  	// 내부에서는 타입을 나눠서 체크하도록 진행해준다
	if(typeof config === "string") console.log(config)
  	else console.log(config.path, config.state)
  	// return이 없는 이유는 위에서 Push타입엔 void로 아무것도 리턴하지 않는다고 설정해서이다
}

추가로 알아야 할 overloading 특징

다른 여러개의 argument를 가지고 있을 때 발생하는 효과

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

// 파라미터의 갯수가 다르기 때문에 c는 옵션같은 의미를 가진다 
// Add를 부를 때, a, b를 부를 수도 있고, 또는 a, b, c를 부를 수도 있기 때문에
// 그렇기 때문에 c는 아마도 number일 것이라는 의미를 넣어줘야한다
// c라는 파라미터는 선택사항이라는 것을 알려준다는 말이다
const add: Add = (a, b, c?:number) =>{ // 파라미터 개수가 다를 때 c?:number처럼 넣어줘야한다
  	if(c) return a + b + c
	return a + b
}

add(1, 2) // 3
add(1, 2, 3) // 6

Polymorphism 다형성

polymorphism이란?

여러가지 다른 형태들
기본적으로 함수는 여러가지 다른 모양을 가지거나 다른 형태를 가지고 있다

함수는 다른 2-3개의 parameter를 가질 수 있고, string이나 object를 첫번째 파라미터로 가질 수 있다
이러한 형태들도 polymorphism 다형성에 속한다

type AllPrint = {
	(arr: number[]): void // 함수는 아무것도 리턴하지 않으니까
  	(arr: boolean[]): void
}
// 배열을 받고 그 배열의 요소를 하나씩 print해주는 함수
type allPrint : AllPrint = (arr) => {
	arr.forEach(i => console.log(i))
}

allPrint([1, 2, 3, 4])
allPrint([true, false, false, true])

만약 여기서 string타입의 배열을 출력하게 되면? 당연히 오류가 난다

그럴때 해결방법은?

  1. type AllPrint에 새로 string타입을 써넣는다
  2. generic을 사용한다

generic

generic이란?
타입스크립트가 어떻게 다형성을 주는지를 의미한다
타입의 placeholder 같은 것을 말한다
concrete type을 사용하는 것 대신 쓸 수 있다
내가 요구한대로 call signatures를 생성해줄 수 있는 도구

* concrete type이란?

number, string, boolean, void, unknwon 등 우리가 자주 접했던 타입들

generic을 왜 사용할까?

call signature를 작성할 때, 들어올 타입을 확실하게 모를 때 generic을 사용한다
원래 코드를 작성하고 함수를 구현하고 사용할때는 concrete type을 사용해야한다
다만, call signature를 작성하는데 concrete type을 알 수 없을때도 분명히 있다
그런 경우 generic을 사용한다
즉, 어떤 타입들이 들어있는지 모르는 배열이 올 경우 직접 모든 call signature를 작성해주기 힘드니까 사용한다
(generic을 사용하면 ts는 내가 작성한 코드를 보고 타입을 알아낸다)

generic을 사용하는 방법

  • ts에 generic을 사용하고 싶다고 알리기
    < T >와 같이 (arr: T): void 앞에 넣어주면 된다
type AllPrint = {
  	// generic을 쓸때는 <>안에 내가 짓고 싶은 이름을 넣는다 보통 T,V를 많이 쓴다
	<TypePlaceholder>(arr: TypePalceholder[]): void
}

type allPrint : AllPrint = (arr) => {
	arr.forEach(i => console.log(i))
}

// 모두 정상적으로 작동
allPrint([1, 2, 3, 4])
allPrint([true, false, false, true])
allPrint(["a", "b", "c"])
allPrint([1, 2, true, false, "hello"])
type AllPrint = {
  	// 만약 allPrint함수에서 return값이 있고 
    // 그 return값 또한 어떠한 타입일지 정확히 모르니까 TypePlaceholder라고 해준다
	<T>(arr: T[]): T
}

type allPrint : AllPrint = (arr) => arr[0]  // 첫 번째 요소만 출력 

// 모두 정상적으로 작동
const a = allPrint([1, 2, 3, 4]) // a는 number타입
const b = allPrint([true, false, false, true]) // b는 boolean타입
const c = allPrint(["a", "b", "c"]) // c는 string타입
const d = allPrint([1, 2, true, false, "hello"]) // d는 number or boolean or string 타입

generic을 하나 더 추가해줄 때

type AllPrint = <T, M>(arr: T[], arr2: M) => T

const allPrint: AllPrint = (arr) => arr[0]

allPrint([1, 2, 3, 4], "x")
allPrint([true, false, "hello"], 2)

타입스크립트는 generic이 처음 사용되는 지점을 기반으로 이 타입이 무엇인지 알게 된다

실제로 generic을 다루는 방법

  • 일일이 generic을 작성한 것과 다르게 실제로는 다른 패키지나 라이브러리를 사용하고, 그 패키지나 라이브러리들이 generic을 통해서 생성된다
  • 다시 말하자면, 라이브러리를 만들거나 다른 개발자가 사용할 기능을 개발하는 경우엔 generic이 유용하다
  • 함수의 call signature 외에 또 어디서 쓸 수 있을까?
type AllPrint = <T>(arr: T[]): T

type allPrint : AllPrint = (arr) => arr[0]  

const a = allPrint([1, 2, 3, 4]) 
const b = allPrint([true, false, false, true]) 
const c = allPrint(["a", "b", "c"]) 
const d = allPrint([1, 2, true, false, "hello"])
// 일반 함수로 대체하는 방법
function allPrint<T>(arr: T[]){
	return a[0]
}

allPrint([1, 2, 3, 4])
// allPrint<number>([1, 2, 3, 4])는 같다

앞에서 공부한 User를 generic을 써보자

type User<V> = {
	name: string
  	extraInfo: E
} 

const hong : User<{favFood}> = {
	name: "hong",
  	extraInfo: {
		favFood: "kimchi"
	}
}

또 다르게 사용하는 방법

type User<V> = {
	name: string
  	extraInfo: E
} 

type HongUser = User<{favFood: string}>

const hong : HongUser = {
	name: "hong",
  	extraInfo: {
		favFood: "kimchi"
	}
}

또또 다르게 사용하는 방법

type User<V> = {
	name: string
  	extraInfo: E
} 

type HongExtra = {
	favFood: string
}

type HongUser = User<HongExtra>

const hong : HongUser = {
	name: "hong",
  	extraInfo: {
		favFood: "kimchi"
	}
}

// 많은 것들이 있는 큰 타입(User)를 하나 가지고 있는데, 그 안의 요소가 하나 달라질 수 있는 타입이라면 generic을 넣으면 된다!!!
const Kim: User<null> = {
	name: "Kim",
  	extraInfo: null
}

Array generic 형식

// 1번
type A = Array<number>
let a: A = [1, 2, 3, 4]

// 2번
funtciont printAllNumbers(arr: Array<number>){}

React에서 generic을 사용하면

useState<number>()

0개의 댓글