이론 (Front-end)/TypeScript

[TypeScript] 함수

이우열 2023. 12. 22. 19:19
728x90
타입스크립트의 함수에 대해서 알아봅시다.

 

 

✅ Call Signatures

함수 위에 마우스를 올렸을 때 보게 되는 것

 

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

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

 

add라는 함수를 만들고 마우스를 올렸을 때

 

함수의 타입을 만들어두고 함수를 구현하기 전에

함수가 어떻게 작동하는지 서술해둘 수 있는 것을 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의 타입이 string이라면 a를 반환하고, number라면 a + b를 반환합니다.

 

위와 같이 call signatures를 두 개로 작성하여 overloading 할 수 있습니다.

 

 

 

✏️ Next.js에서의 예시

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

Router.push("/home")

 

위와 같이 push 함수는 object로 보낼 수도 있고 string으로 보낼 수도 있습니다.


 

원리는 무엇일까요?

 

✏️ 원리

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

type Push = {
    (path: string): void
    (config: Config): void
}

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

 

push 함수의 형태를 간략하게 요약해보면 위의 형태와 같습니다.

 

push 함수는 Push 타입을 가지고 config를 인자로 받습니다.

이 때 config의 타입이 string이라면 config가 path가 되고

config의 타입이 object라면 config.path로 path에 접근할 수 있게 됩니다.

 

 

✅ 다른 개수의 파라미터를 가질 때

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
}

 

파라미터의 개수가 다를 경우 남는 파라미터는 옵션이자 선택사항이 됩니다.

함수를 생성할 때 ?를 붙여 옵션으로 만들어주어야 합니다.

 

 

✅ Polymorphism(다형성)

Poly: many, several, much, multi
morphos: form, structure, shape
여러가지 다른 구조, 여러가지 다른 형태

 

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"]) // 에러 발생

 

superPrint 함수는 SuperPrint 타입을 가집니다.

arr라는 number 배열과 boolean 배열을 인자로 가질 수 있고 리턴 타입은 void입니다.

 

superPrint 함수를 실행하면 인자로 받은 arr의 요소들을 각각 하나씩 콘솔 로그로 출력하게 됩니다.

 

위의 세 개의 호출에서 마지막 호출은 SuperPrint 타입에서 어긋나므로 에러가 발생합니다.

 

 

✏️ Concrete Type

number, string, boolean, void, unknown 등과 같은 타입

 

✏️ Generic Type

타입의 placeholder와 같은 것
concrete type을 대신해서 사용 가능하며 타입스크립트에서 추론하여 함수를 사용

 

✏️ Generic Type을 사용하는 이유

call signature를 작성할 때, 들어올 확실한 타입을 모를 경우 generic type을 사용합니다.

generic type은 선언 시점이 아닌 생성 시점에서 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 해줍니다.

 

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

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

superPrint([1, 2, 3, 4])
superPrint([true, false, true])
superPrint(["a", "b", "c"])
superPrint([1, 2, true, false])

 

Generic을 사용하는 방법은 call signature 앞에 꺽쇠를 사용하여 이름을 정해주고

들어올 타입에 지정한 이름을 작성하면 됩니다.

 

 

type SuperPrint = {
    <TypePlaceholder>(arr: TypePlaceholder[]): TypePlaceholder
    // <T>(arr: T[]): T
    // 위와 같이 T를 자주 사용
}

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

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, false])

 

return 값을 generic으로 사용하고 싶다면 return 타입에 꺽쇠 안에 설정한 이름을 넣어주면 됩니다.

 

 

type SuperPrint = <T, V>(a: T[], b: V) => T

 

타입스크립트는 제네릭을 처음 인식했을 때와 제네릭의 순서를 기반으로 제네릭의 타입을 알게 됩니다.

타입을 말할 필요 없이 제네릭의 이름만 말해주면 됩니다.

 

 

✏️ any와의 차이점

any와는 다르며 우리가 하는 요청에 따라 call signature를 생성하는 것입니다.

any를 사용하여 모든 타입을 허용하며 오류를 없애는 것이 아닙니다.

 

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

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

const a = superPrint([1, 2, 3, 4])
a.toUpperCase() // 에러를 알려주지 않지만 실행 후 에러 발생

// type SuperPrint = <T>(arr: T[]) => T
// 제네릭을 사용하면 에러를 알려줌

 

 

✅ Generic Type 실제 사용 방법

function superPrint<T>(a: T[]) {
    return a[0]
}
superPrint라는 함수에서 제네릭으로 T를 명시하고
T 타입들의 배열을 입력받은 뒤 배열의 첫 번째 요소를 리턴합니다.

 

 

type Player<E> = {
    name: string,
    extraInfo: E
}

type NicoExtra = {
    favFood: string
}

type NicoPlayer = Player<NicoExtra>

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

const lynn: Player<null> = {
    name: "lynn",
    extraInfo: null
}
제네릭은 타입에서도 사용 가능합니다.

Player에서 E라는 제네릭은 extraInfo의 값(value)으로 들어가게 됩니다.
NicoPlayer는 NicoExtra라는 제네릭을 가진 Player 타입입니다.

 

nico라는 객체는 NicoPlayer 타입을 가지며 NicoPlayer 타입은 기존 Player의 제네릭 자리에 NicoExtra 타입이 들어갑니다.

NicoExtra 타입은 favFood라는 문자열을 가진 객체입니다.

 

lynn이라는 객체는 Player 타입의 제네릭 자리에 null을 넣어 extraInfo를 null로 선언한 객체입니다.

 

728x90

'이론 (Front-end) > TypeScript' 카테고리의 다른 글

[TypeScript] Interface(인터페이스)  (0) 2023.12.27
[TypeScript] Static 키워드  (0) 2023.12.27
[TypeScript] 타입 확장  (1) 2023.12.23
[TypeScript] Class(클래스)  (1) 2023.12.23
[TypeScript] 타입 시스템  (0) 2023.07.05