Frontend/TypeScript

[TypeScript] 3. 함수와 타입

ayeongjin 2025. 1. 25. 00:36

함수의 타입을 정의하는 방법

자바스크립트 : 어떤 매개변수를 받고, 어떤 결과값을 반환하는지 설명 타입스크립트 : 어떤 타입의 매개변수를 받고, 어떤 타입의 값을 반환하는지 설명

// 함수를 설명하는 가장 좋은 방법
// 어떤 매개변수를 받고, 어떤 결과값을 반환하는지 설명

function func(a: number, b: number): number {
  return a + b;
}
  • 함수의 반환값 타입은 자동으로 추론되기 때문에 다음과 같이 생략 가능
function func(a: number, b: number) {
  return a + b;
}

1. 화살표 함수 타입 정의하기

화살표 함수의 타입 정의 방식은 함수 선언식과 동일

const add = (a: number, b: number): number => a + b;
  • 화살표 함수 역시 반환값의 타입은 자동으로 추론하므로 생략 가능
1const add = (a: number, b: number) => a + b;

2. 매개변수 기본값 설정하기

함수의 매개변수에 기본값이 설정되어있으면 타입이 자동으로 추론되므로 생략 가능

function introduce(name = "이정환") {
	console.log(`name : ${name}`);
}
  • 기본값과 다른 타입으로 매개변수의 타입을 정의하면 오류 발생
function introduce(name:number = "이정환") {
	console.log(`name : ${name}`);
}
  • 기본값과 다른 타입의 값을 인수로 전달해도 오류 발생
function introduce(name = "이정환") {
  console.log(`name : ${name}`);
}

introduce(1); // 오류

3. 선택적 매개변수 설정하기

매개변수의 이름뒤에 물음표(?)를 붙여주면 선택적 매개변수가 되어 생략이 가능

function introduce(name = "이정환", tall?: number) {
  console.log(`name : ${name}`);
  console.log(`tall : ${tall}`);
}

introduce("이정환", 156);

introduce("이정환");
  • 위 코드의 tall 같은 선택적 매개변수의 타입은 자동으로 undefined와 유니온 된 타입으로 추론
  • 따라서 tall의 타입은 현재 number | undefined가 된다.
  • 그러므로 이 값이 number 타입의 값일 거라고 기대하고 사용하려면 다음과 같이 타입 좁히기가 필요
function introduce(name = "이정환", tall?: number) {
  console.log(`name : ${name}`);
  if (typeof tall === "number") {
    console.log(`tall : ${tall + 10}`);
  }
}

4. 나머지 매개변수

  • 나머지 매개변수의 타입 정의 1
function getSum(...rest: number[]) {
  let sum = 0;
  rest.forEach((it) => (sum += it));
  return sum;
}
  • 나머지 매개변수의 타입 정의 2
function getSum(...rest: [number, number, number]) {
  let sum = 0;
  rest.forEach((it) => (sum += it));
  return sum;
}

getSum(1, 2, 3)    // ✅
getSum(1, 2, 3, 4) // ❌

 


 

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

1. 함수 타입 표현식

함수 타입을 타입 별칭과 함께 별도로 정의가능하고 이를 함수 타입 표현식(Function Type Expression)이라고 부른다.

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

const add: Add = (a, b) => a + b;
  • 변수 add의 타입을 함수 타입 표현식으로 정의한 함수 타입으로 정의
  • 함수 타입 표현식을 이용하면 함수 선언 및 구현 코드와 타입 선언을 분리할 수 있어 유용
  • 함수 타입 표현식은 다음과 같이 여러개의 함수가 동일한 타입을 갖는 경우에 요긴하게 사용 가능
// 사용 전

const add = (a: number, b: number) => a + b;
const sub = (a: number, b: number) => a - b;
const multiply = (a: number, b: number) => a * b;
const divide = (a: number, b: number) => a / b;
// 사용 후

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

const add: Operation = (a, b) => a + b;
const sub: Operation = (a, b) => a - b;
const multiply: Operation = (a, b) => a * b;
const divide: Operation = (a, b) => a / b;

2. 호출 시그니쳐

호출 시그니쳐(Call Signature)는 함수 타입 표현식과 동일하게 함수의 타입을 별도로 정의하는 방식

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

const add2: Operation2 = (a, b) => a + b;
const sub2: Operation2 = (a, b) => a - b;
const multiply2: Operation2 = (a, b) => a * b;
const divide2: Operation2 = (a, b) => a / b;
  • 자바스크립트에서는 함수도 객체이기 때문에, 위 코드처럼 객체를 정의하듯 함수의 타입을 별도로 정의 가능
  • 참고로 이때 다음과 같이 호출 시그니쳐 아래에 프로퍼티를 추가 정의하는 것도 가능하다.
  • 이렇게 할 경우 함수이자 일반 객체를 의미하는 타입으로 정의되며 이를 하이브리드 타입이라고 부른다.
type Operation2 = {
  (a: number, b: number): number;
  name: string;
};

const add2: Operation2 = (a, b) => a + b;

add2(1, 2);
add2.name;

 


 

함수 타입의 호환성

함수 타입의 호환성이란 특정 함수 타입을 다른 함수 타입으로 괜찮은지 판단하는 것을 의미 다음 2가지 기준으로 함수 타입의 호환성을 판단한다.

  1. 두 함수의 반환값 타입이 호환되는가?
  2. 두 함수의 매개변수의 타입이 호환되는가?

기준 1 : 반환값 타입이 호환되는가?

A와 B 함수 타입이 있다고 가정할 때 A 반환값 타입이 B 반환값 타입의 슈퍼타입이라면 두 타입은 호환된다

type A = () => number;
type B = () => 10;

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

a = b; // ✅
b = a; // ❌
  • A의 반환값 타입은 Number, B의 반환값 타입은 Number Literal이다
  • 따라서 변수 a에 b를 할당하는 것은 가능하나 반대로는 불가능

기준 2 : 매개변수의 타입이 호환되는가?

두 함수의 매개변수의 개수가 같은지 다른지에 따라 두가지 유형으로 나뉨

2-1) 매개변수의 개수가 같을 때

두 함수 타입 C와 D가 있다고 가정할 때 두 타입의 매개변수의 개수가 같다면 C 매개변수의 타입이 D 매개변수 타입의 서브 타입일 때에 호환된다

type C = (value: number) => void;
type D = (value: 10) => void;

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

c = d; // ❌
d = c; // ✅
  • C 매개변수의 타입은 Number, D 매개변수의 타입은 Number Literal 이다.
  • 따라서 C 매개변수의 타입이 D 매개변수의 슈퍼타입이므로 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; // ❌
dogFunc = animalFunc; // ✅
  • animalFunc에 dogFunc를 할당하는 것은 불가능
  • dogFunc의 매개변수 타입이 animalFunc 매개변수 타입보다 작은 서브타입이기 때문
let animalFunc = (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; // ❌

 


 

함수 오버로딩

하나의 함수를 매개변수의 개수나 타입에 따라 다르게 동작하도록 만드는 문법

/**
 * 함수 오버로딩
 * 같은 함수를 매개변수의 개수나 타입에 따라
 * 여러가지 버전으로 만드는 문법
 * -> 하나의 함수 func
 * -> 일단 모든 매개변수는 넘버타입
 * -> Ver1. 매개변수가 1개일 때에는 매개변수에 20을 곱한 값을 출력
 * -> Ver2. 매개변수가 3개일 때에는 모든 매개변수를 더한 값을 출력
 */

1. 오버로드 시그니처

타입스크립트에서 함수 오버로딩을 구현하려면 먼저 다음과 같이 버전별 오버로드 시그니쳐를 만들어 줘야 함

// 버전들 -> 오버로드 시그니쳐
function func(a: number): void;
function func(a: number, b: number, c: number): void;
  • 이렇게 구현부 없이 선언부만 만들어둔 함수를 ‘오버로드 시그니쳐’라고 한다.
  • 위 코드에서는 2개의 오버로드 시그니쳐를 만들었으며 각각 함수의 버전을 의미한다.
  • (위 코드는 func 함수는 매개변수를 1개 받는 버전과 3개 받는 2개의 버전이 있다고 알리는 것 과 같다)

2. 구현 시그니처

실제로 함수가 어떻게 실행될 것인지를 정의하는 부분

  • 오버로드 시그니쳐를 만들었다면 다음으로는 구현 시그니쳐를 만들어줘야 한다.
// 버전들 -> 오버로드 시그니쳐
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);        // ✅ 버전 1 - 오버로드 시그니쳐
func(1, 2);     // ❌
func(1, 2, 3);  // ✅ 버전 3 - 오버로드 시그니쳐
  • 구현 시그니쳐의 매개변수 타입은 모든 오버로드 시그니쳐와 호환되도록 만들어야 한다.
  • 따라서 위 코드에서는 매개변수 b와 c를 선택적 매개변수로 만들어 매개변수를 하나만 받는 첫번째 오버로드 시그니쳐와도 호환되도록 함

 


 

사용자 정의 타입 가드

참 또는 거짓을 반환하는 함수를 이용해 우리 입맛대로 타입 가드를 만들 수 있도록 도와주는 타입스크립트의 문법

// 사용 전
// Dog 타입의 프로퍼티가 다음과 같이 중간에 이름이 수정되거나 추가 또는 삭제될 경우에
// 타입 가드가 제대로 동작하지 않을 수도 있다.

type Dog = {
  name: string;
  isBark: boolean;
};

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

type Animal = Dog | Cat;

function warning(animal: Animal) {
  if ("isBark" in animal) {
    console.log(animal.isBark ? "짖습니다" : "안짖어요");
  } else if ("isScratch" in animal) {
    console.log(animal.isScratch ? "할큅니다" : "안할퀴어요");
  }
}
// 사용 후

type Dog = {
  name: string;
  isBarked: boolean; // isBark -> isBarked
};

// Dog 타입인지 확인하는 타입 가드
function isDog(animal: Animal): animal is Dog {
  return (animal as Dog).isBark !== undefined;
}

// Cat 타입인지 확인하는 타입가드
function isCat(animal: Animal): animal is Cat {
  return (animal as Cat).isScratch !== undefined;
}

function warning(animal: Animal) {
  if (isDog(animal)) {
    console.log(animal.isBark ? "짖습니다" : "안짖어요");
  } else {
    console.log(animal.isScratch ? "할큅니다" : "안할퀴어요");
  }
}
  • isDog 함수는 매개변수로 받은 값이 Dog 타입이라면 true 아니라면 false를 반환
  • 이때 반환값의 타입으로 animal is Dog 를 정의하면 이 함수가 true를 반환하면 조건문 내부에서는 이 값이 Dog 타입임을 보장한다는 의미
  • 따라서 warning 함수에서 isDog 함수를 호출해 매개변수의 값이 Dog 타입인지 확인하고 타입을 좁힐 수 있다.