#3. Functions

해피데빙·2022년 7월 15일
0

Typescript

목록 보기
7/9

Call Signatures

  • 함수에서 매개변수와 리턴 값의 타입을 미리 정해주는 함수 형태
  • 마우스를 함수 위에 둘 때 나타나는 것
  • 어떻게 구현되는 것이 아니라 매개변수, 리턴값의 타입을 모두 알려준다

type Add = (a:number, b:number) => number; //call signature
type Add = { (a:number, b:number) //매개변수 : number//리턴값 } //더 길게 쓰는 방법
const add:Add = (a,b) => a+b //call signature에서 정의한 것과 같은 타입이 들어간다

function add(a : number , b : number) //:number 필요 없음 (당연히 리턴 값이 number니까)//{ 
	return a+b
}

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

타입을 직접 안 쓰고 싶다면? 
//
type Add = (a:number, b:number) => number; 

const add:Add = (a,b) => a+b //위와 같은 형태로 타입이 들어간다
const add:Add = (a,b) => {a+b} //리턴 값이 객체가 되어 잘못 사용한 거니까 리턴값의 타입이 void라고 알려준다

Overloading

  • 오버로딩을 직접 작성하지는 않고 외부 라이브러리에서 오버로딩이 많이 사용된다
    ex. Next.js
  • 오버로딩이란?
    - 함수가 서로 다른 여러 개의 call signatures를 가지고 있을 때 일어난다
    • 같은 함수가 여러 매개변수로, 여러 타입을 이용할 수 있는 것
type Add = (a:number, b:number) => number; //call signature
type Add = { 
(a:number, b:number):number 
} //더 길게 쓰는 방법 
=> 이 둘이 혼용될 수 있는 이유 : 오버로딩??

type Add = { 
(a:number, b:number):number 
(a:number, b:string):number //b는 number또는 string
} // 이렇게 call signature가 여러 개 있으면 오버로딩 
 
const add : Add = (a,b) => a+b //error : b가 string일수도 있으니까 
const add : Add = (a,b) => {
	if(typeof b === "string") return a 
    return a+b //"number"면 
} //이건 완전 비추

//Next.js에서는 아래 두개가 다 가능하다 : 오버로딩의 예시
type Config = {
	path:string, 
    state:object
}

type Push = {
	(path:string):void //바로 들어오면 path로
    (config: Config):void //객체 형태 안에 path, state 속성이 있으면 config로
}

const push:Push = (config) => { 
	if(typeof config === "string"){ console.log(config) } 
    else {
    	console.log(config.path, config.state)
    }
} 
//이렇게 다 다른 call signature를 가지고 있는 함수 : 오버로딩이 발생

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

Router.push('/home')


만약 매개변수의 수가 다른 call signature가 여러 개 있다면?? 
type Add = { 
	(a:number, b:number) : number
    (a:number, b:number, c:number) : number
}

const add:Add = (a,b,c) =>{ 
//두개의 값만 사용하더라도 error 
//두개의 파라미터만 넣어도 동작하므로 나머지 한 파라미터가 optional이라는 것을 명시해줘야 한다
	return a
}//error 

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

const add:Add = (a,b,c?:number) =>{ 
//이렇게 optional한 타입이라는 것을 명시해줘야 한다
if(c)return a+b+c
	return a+b
}//error 

Polymorphism

  • 다형성

  • poly : many, several, much, multi etc.

  • morphos : form, structure, shape etc.

  • a function that has diff shapes, forms
    ex. 다양한 개수의 파라미터, 파라미터의 타입이 여러개 올 수 있을 때

  • 예제 : 배열의 요소를 하나씩 출력하는 것

Generics

  • 타입을 미리 주지 않고 placeholder를 주는 방법
  • 사용할 때 call signature를 정할 수 있는 방법
  • '제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다.'
type SuperPrint = { 
<TypePlaceholder>(arr : TypePlaceholder[]) => TypePlaceholder
 }
  const hello : SuperPrint = (arr) => arr[0]
  const a = hello([1,2,3]) 
  //<number>(arr : number[]) => number 
  const b = hello(['1',2,true]) 
  //<number | string | boolean>(arr:number | string | boolean[]) => number | string | boolean

제네릭 2개를 추가하고 싶을 때
type SuperPrint = <T, M>(a :T[], b:M) => T
superPrint([1,2,3,4], "x") //첫번째 원소의 타입을 리턴한다

cf. 그럼 any와다른 게 없지 않나?
any를 쓰면 나중에 타입과 무관한 메서드를 쓸 수 있다
type SuperPrint = (arr : any[]) => any
ex. 위의 경우에 a.toUpperCase()를 할 수 있게 된다. a의 타입이 후에 정해지는 게 아니라 계속 any니까

type SuperPrint = {
	(arr  : number[]): void //리턴하는 값이 없는 함수
    (arr : boolean[]): void 
}

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

superPrint([1,2,3,4])// 가능 
superPrint([true, false, true])//가능

superPrint(["a","b","c"])//불가능
이렇게 정하지 않은 타입들을 사용하거나, 다양한 타입들이 섞여 있는 배열을 가능하게 하려면 어떻게 해야 할까? 
-> generic을 사용한다!
: placeholder를 정해주고 타입스크립트가 타입을 추론할 수 있도록 한다 

type SuperPrint = {
	1. call signature 앞에 generic을 사용한다고 표현한다  
    <Generic>(arr : Generic[]):void
    <T>(arr : T[]):void
    <Potato>(arr : Potato[]):void
}

2. 타입스크립트의 요소를 확인하면서 타입을 정해준다 : Generic자리에 타입을 넣고 : 뒤에 넣는다 
ex. <number>(arr : number[]):void

superPrint([1,2,3,4])// 가능 
superPrint([true, false, true])// 가능 
superPrint(["a","b","c"])// 가능 
superPrint([1,2,true,false,"hello"]) //<number | bool | string>(arr : number | bool | string[]):void

3. 리턴 값의 타입도 Generic으로 해줄 수 있다 

type SuperPrint = {
	1. call signature 앞에 generic을 사용한다고 표현한다  
    <Generic>(a: Generic[]):Generic  
     
}

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


const a = superPrint([1,2,3,4])// number
const b =superPrint([true, false, true])// bool
const c =superPrint(["a","b","c"])// string
const d =superPrint([1,2,true,false,"hello"]) //<number | bool | string>(arr : number | bool | string[]):number | bool | string

Conclusions

실제로는 제네릭을 이횽해서 직접 Call Signature를 만드는 일을 그렇게 많이 하지 않는다
대부분 다른 사람들이 만든 라이브러리들에서 이미 만들어진 형태의 Call Signature를 이용할 뿐
ex. React, Next 등

제네릭을 call signature 외에서 사용할 수 있는 방법


typescript가 타입을 추론할 수 있도록 한다

const a = superPrint<number>([1,2,3,4])
const a = superPrint([1,2,3,4])
//둘다 같다
type Player<E> = {
	name:string   
    extraInfo:E 
}

type NicoExtra = {
	favFood:string
}

1)const nico:Player<{favFood:string}> = {
	name:"nico", 
    extraInfo:{
    	favFood:"kimchi"
    }
}

2)type NicoPlayer = Player<{favFood:string}>
const nico:NicoPlayer = {
  name:"nico", 
      extraInfo:{
          favFood:"kimchi"
      }
}

3)type NicoPlayer = Player<NicoExtra>

const nico:NicoPlayer = {
  name:"nico", 
      extraInfo:{
          favFood:"kimchi"
      }
}


//위의 세개가 같다


제네릭을 만들 수 있는 다른 방법
type arrNumbers = Array<number>
let a : A = [1,2,3,4]

function printAllNumbers(arr:Array<number>){}
function printAllNumbers(arr:number[]){}
//위의 두개가 같다

react + typescript
: useState<number>() //이렇게 사용 
profile
노션 : https://garrulous-gander-3f2.notion.site/c488d337791c4c4cb6d93cb9fcc26f17

0개의 댓글