본문 바로가기

Frontend/Typescript

함수와 타입

함수 타입

아래 코드에는 함수 선언식, 화살표 함수에 타입을 정의하는 방법, 그리고 함수의 매개변수에 타입을 정의하는 방법이 나와있다.

특히, 함수의 매개변수에 rest parameter를 사용하는 예도 있으니 참고하면 좋을 것 같다.

/**
 * 함수 타입 정의
 */

// 함수를 설명하는 가장 좋은 방법
// 어떤 타입의 매개변수를 받고, 어떤 타입의 결과값을 반환하는지 설명
function func(a: number, b: number): number {
    return a + b;
}

/**
 * 화살표 함수의 타입을 정의하는 방법
 */

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

/**
 * 함수의 매개변수
 */

function introduce(name = '이정환', height?: number): void {
    console.log(`name: ${name}`);
    if (typeof height === 'number') {
        console.log(`height: ${height}`);
    }
}

introduce('이정환', 175);
introduce('이정환');

// rest parameter
function getSum1(...rest: number[]) {
    let sum = 0;

    rest.forEach((it) => sum += it);

    return sum;
}

function getSum2(...rest: [number, number, number]) {
    let sum = 0;

    rest.forEach((it) => sum += it);

    return sum;
}

getSum1(1, 2, 3);  // 6
getSum1(1, 2, 3, 4);  // 10

 

함수의 매개변수에는 선택적 매개변수를 넣어줄 수가 있는데, 선택적 매개변수는 필수 매개변수 앞에 위치하면 안 된다.

 

함수 타입 표현식과 호출 시그니처

함수의 매개변수와 반환 타입을 정의해주는 경우 다음과 같이 함수 타입 표현식호출 시그니처로 나타낼 수 있다.

/**
 * 함수 타입 표현식
 */

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

const add1: (a: number, b: number) => number = (a, b) => a + b;
const sub1: Operation = (a, b) => a - b;
const mul1: Operation = (a, b) => a * b;
const div1: Operation = (a, b) => a / b;

/**
 * 호출 시그니처 (콜 시그니처)
 */

type Operation2 = {
    (a: number, b: number): number;
}

const add2: Operation2 = (a, b) => a + b;
const sub2: Operation2 = (a, b) => a - b;
const mul2: Operation2 = (a, b) => a * b;
const div2: Operation2 = (a, b) => a / b;

 

함수 타입의 호환성

함수 타입의 호환성을 판단하는 경우에는 두 가지 사항을 고려해야 한다.

함수의 반환값을 기준으로 호환되는지 판단하는 경우는, 일반적인 경우처럼 업 캐스팅은 허용되고, 다운 캐스팅은 허용되지 않는다.

하지만, 함수의 매개변수를 기준으로 호환되는지 판단하는 경우는 다운 캐스팅은 허용되는데 업 캐스팅은 허용되지 않는다.

/**
 * 함수 타입 호환성
 * 특정 함수 타입을 다른 함수 타입으로 취급해도 괜찮은가를 판단하는 것
 * 1. 반환값의 타입이 호환되는가
 * 2. 매개변수의 타입이 호환되는가
 */

// 기준 1. 반환값이 호환되는가
type A = () => number;
type B = () => 10;

let a: A = () => 10;
let b: B = () => 10;

// 업 캐스팅
a = b;
// 다운 캐스팅
b = a;  // 'A' 형식은 'B' 형식에 할당할 수 없습니다.
        // 'number' 형식은 '10' 형식에 할당할 수 없습니다.

// 기준 2. 매개변수가 호환되는가
// 2-1. 매개변수의 개수가 같을 때
type C = (value: number) => void;
type D = (value: 10) => void;

let c: C = (value) => { };
let d: D = (value) => { };

// 업 캐스팅
c = d;  // 'D' 형식은 'C' 형식에 할당할 수 없습니다.
        // 'value' 및 'value' 매개 변수의 형식이 호환되지 않습니다.
        // 'number' 형식은 '10' 형식에 할당할 수 없습니다.
// 다운 캐스팅
d = c;

type Animal = {
    name: string;
}

type Dog = {
    name: string;
    color: string;
}

let animalFunc = (animal: Animal) => { 
    console.log(animal.name);
};

let dogFunc = (dog: Dog) => { 
    console.log(dog.name);
    console.log(dog.color);
};

// 업 캐스팅
animalFunc = dogFunc;  // '(dog: Dog) => void' 형식은 '(animal: Animal) => void' 형식에 할당할 수 없습니다.
                       // 'dog' 및 'animal' 매개 변수의 형식이 호환되지 않습니다.
                       // 'color' 속성이 'Animal' 형식에 없지만 'Dog' 형식에서 필수입니다.

// let testFunc1 = (animal: Animal) {
//     console.log(animal.name);
//     console.log(animal.color);
// }

// 2-2. 매개변수의 개수가 다를 때
type Func1 = (a: number, b: number) => void;
type Func2 = (a: number) => void;

let func1: Func1 = (a, b) => { };
let func2: Func2 = (a) => { };

func1 = func2;
func2 = func1;  // 'Func1' 형식은 'Func2' 형식에 할당할 수 없습니다.
                // Target signature provides too few arguments. 
                // Expected 2 or more, but got 1.

 

함수 오버로딩

/**
 * 함수 오버로딩
 * 하나의 함수를 매개변수의 개수나 타입에 따라
 * 여러가지 버전으로 만드는 문법
 */

// 오버로드 시그니처
function func(a: number): void;
function func(a: number, b: number, c: number): void;

// 실제 구현부 => 구현 시그니처
function func(a: number, b?: number, c?: number) { 
    if (typeof b === 'number' && typeof c === 'number') {
        console.log(a + b + c);
    } else {
        console.log(a * 20);
    }
}

func(1);
func(1, 2, 3);

 

사용자 정의 타입 가드

/**
 * 사용자 정의 타입 가드
 */
type Dog = {
    name: string;
    isBark: boolean;
}

type Cat = {
    name: string;
    isScratch: boolean;
}

type Animal = Dog | Cat;

function isDog(animal: Animal): animal is Dog {
    return (animal as Dog).isBark !== undefined;
}

function isCat(animal: Animal): animal is Cat {
    return (animal as Cat).isScratch !== undefined;
}

function warning(animal: Animal) {
    if (isDog(animal)) {
        animal;  // Dog 타입
    } else if (isCat(animal)) {
        animal;  // Cat 타입
    }
}

warning 함수를 보면 매개변수로 Animal 타입을 받는다.

Dog 타입인 경우와 Cat 타입인 경우 동작을 다르게 수행하고 싶을 수 있는데, 이때 사용자 정의 타입 가드를 사용하면 가독성 있게 코드를 구현해볼 수 있다.

 

isDog 함수와 isCat 함수가 그 예이다.

각 함수는 Animal 타입의 인수를 받아서 타입 좁히기를 수행하는 함수이다.

반환값 타입을 보면 animal is Dog/Cat이라고 되어있는데 animal 타입이 타입 좁히기를 통해 Dog/Cat 타입인지를 판단해서 반환값 타입을 정한다는 것이다.

함수 내부 구현문에 대해서 설명해보자면, 인수로 받은 animal에 isBark라는 속성이 있다면 Dog 타입, animal에 isScratch라는 속성이 있다면 Cat 타입이 될 것이다.

그런데, Cat 타입에는 isBark 속성이 없고, Dog 타입에는 isScratch 속성이 없기 때문에 animal.isBark나 animal.isScratch를 써주게 되면 에러가 발생한다.

따라서 타입 단언을 사용해서 (animal as Dog).isBark, (animal as Cat).isScratch로 나타내면 이 문제를 해결할 수 있다.

해당 타입이 undefined인지 아닌지를 판단해서 반환값 타입을 정하면 된다.

 

그래서, Dog 타입인 경우는 Animal -> Dog로, Cat 타입인 경우에는 Animal -> Cat으로 타입 좁히기가 잘 수행된다.

'Frontend > Typescript' 카테고리의 다른 글

제네릭  (0) 2023.06.20
인터페이스와 클래스  (0) 2023.06.20
타입스크립트 이해하기  (0) 2023.06.20
타입스크립트 기본  (0) 2023.06.20
타입스크립트 개론  (0) 2023.06.19