logoRawon_Log
홈블로그소개

Built with Next.js, Bun, Tailwind CSS and Shadcn/UI

Typescript

Typescript Function

Rawon
2025년 11월 26일
목차
🎯 함수 타입 정의
함수 매개변수
Rest Parameters (나머지 매개변수)
🎯 함수 타입 표현식
🎯 호출 시그니처
🎯 함수 타입의 호환성
기준1. 반환값 타입 호환 여부
기준2. 매개변수 타입 호환 여부
2-1. 매개변수의 개수가 같을 때
2-2. 매개변수의 개수가 다를 때
🎯 함수 오버로딩
함수 오버로드 시그니처
구현 시그니처
🎯 사용자 정의 타입 가드
사용자 정의 타입가드가 필요한 이유
커스텀 타입 가드 만들기
animal is Dog 구문의 의미

목차

🎯 함수 타입 정의
함수 매개변수
Rest Parameters (나머지 매개변수)
🎯 함수 타입 표현식
🎯 호출 시그니처
🎯 함수 타입의 호환성
기준1. 반환값 타입 호환 여부
기준2. 매개변수 타입 호환 여부
2-1. 매개변수의 개수가 같을 때
2-2. 매개변수의 개수가 다를 때
🎯 함수 오버로딩
함수 오버로드 시그니처
구현 시그니처
🎯 사용자 정의 타입 가드
사용자 정의 타입가드가 필요한 이유
커스텀 타입 가드 만들기
animal is Dog 구문의 의미

이 글은 아래 강의를 바탕으로 정리한 글입니다. 🤗

plain
https://inf.run/UGoRu

🎯 함수 타입 정의

💡 함수의 타입을 정의하는 방법은 "어떤 타입의 매개변수를 받고 어떤 타입의 결과값을 반환하는지를 정의" 하는 것입니다.

typescript
function add1(a: number, b: number): number {
  return a + b;
}

// 화살표 함수의 타입 정의
const addFunc = (a: number, b: number): number => a + b;

함수 매개변수

매개변수에 기본값을 할당하면 타입이 자동으로 추론되므로 타입 정의를 생략해도 됩니다.

그리고 매개변수의 이름 뒤에 ? 를 붙여주면 선택적 매개변수가 되어 생략이 가능합니다.

⚠️ 다만, 주의해야할 사항으로 선택적 매개변수 뒤에 필수 매개변수가 위치하면 오류가 발생하니 반드시 선택적 매개변수는 필수 매개변수 뒤에 작성해주어야 합니다.

typescript
function introduce(name = "John", tall?: number) {
  console.log(`안녕하세요, 저는 ${name}입니다.`);
  // tall을 선택적 매개변수로 만듦으로서 tall의 타입은 number | undefined 타입이 됨.
  // 그래서 만약 tall에 숫자를 더한다고 하면 오류가 발생.(undefined + number는 불가능)
  // 이런 경우에는 조건문을 사용하여 타입가드를 만들어 타입을 좁혀주어야 함.
  if (typeof tall === "number") {
    console.log(`키는 ${tall + 10}cm입니다.`);
  }
  console.log(`키는 ${tall}cm입니다.`);
}

introduce("John", 170);
introduce("Jane");

Rest Parameters (나머지 매개변수)

spread 연산와 마찬가지로 ... 을 앞에 붙이면 되나 spread 연산자는 반복가능한 객체나 배열을 개별요소로 분리하지만 Rest Parameters 는 함수에 전달한 인수들을 순차적으로 배열에 저장 합니다. (개별 요소를 배열로 묶음)

이때, rest 파라미터의 타입은 다음과 같이 정의할 수 있습니다.

typescript
function getSum(...rest: number[]) {
  // 배열의 모든 요소를 더하여 반환
  return rest.reduce((acc, curr) => acc + curr, 0);
}

getSum(1, 2, 3, 4, 5);
getSum(1, 2, 3);

🎯 함수 타입 표현식

다음과 같이 함수 타입을 타입 별칭 과 함께 별도로 정의할 수 있고 이를 함수 타입 표현식(Function Type Expression) 이라고 합니다.

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

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

✅ 이렇게 함수 타입 표현식을 사용하면 함수 선언 및 구현 코드와 타입 선언을 분리할 수 있어 유용합니다.

물론 함수 타입 표현식이 반드시 타입 별칭과 함께 사용될 필요는 없습니다.

typescript
// 타입 별칭을 활용하지 않은 함수 타입 표현식
const divide: (a: number, b: number) => number = (a, b) => a / b;

🎯 호출 시그니처

호출 시그니처는 함수 타입 표현식과 동일하게 함수의 타입을 별도로 정의하는 방식입니다.

JS의 함수도 객체이기 때문에 마치 객체 타입을 정의하는 것처럼 함수 타입도 정의할 수 있습니다.

typescript
type Operation2 = {
  (a: number, b: number): number;
  // 호출 시그니쳐 아래에 프로퍼티를 추가 정의하는 것도 가능합니다.
  // 이렇게 할 경우 함수이자 일반 객체를 의미하는 타입으로 정의되며 이를 하이브리드 타입이라고 부릅니다.
  name: string;
};

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;

🎯 함수 타입의 호환성

📌 함수 타입의 호환성이란 특정 함수 타입을 다른 함수 타입으로 취급해도 괜찮은가를 판단하는 것을 의미합니다.

아래 2가지 기준을 모두 충족해야 두 함수의 타입이 호환된다라고 할 수 있습니다.

  1. 반환값의 타입 호환 여부
  2. 매개변수의 타입 호환 여부

기준1. 반환값 타입 호환 여부

아래와 같이 A, B 함수 타입이 있다고 가정했을 때, A의 반환값 타입이 B 반환값 타입의 슈퍼타입이라면 두 타입은 호환된다고 할 수 있습니다.

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

let a: A = () => 10; // number 타입
let b: B = () => 10; // number 리터럴 타입

a = b; // ✅ number 리터럴 타입을 number 타입으로 취급 (업캐스팅) 가능
b = a; // ❌ number 타입을 number 리터럴 타입으로 취급 (다운캐스팅) 불가능

A의 반환값 타입은 Number, B의 반환값 타입은 Number Literal 입니다.

따라서 변수 a에 b를 할당하는 것은 가능하나 반대로는 불가능 합니다.

기준2. 매개변수 타입 호환 여부

매개변수 타입이 호환되는지 판단할 때에는 두 함수의 매개변수의 갯수가 같은지 다른지에 따라 두가지 유형으로 나뉘게 됩니다.

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

만약 두 타입의 매개변수의 갯수가 같다면 C 매개변수의 타입이 D 매개변수 타입의 서브 타입일 때에 호환됩니다.

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

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

// c = d; // ❌ D 타입을 C 타입으로 취급 (업캐스팅) 불가능,
// 반환값 타입을 기준으로 호환성을 판단할 때와는 달리 매개변수 타입을 기준으로 호환성을 판단할 때는 다운캐스팅만 가능
d = c; // ✅ C 타입(number)을 D 타입(number 리터럴)으로 취급 (다운캐스팅) 가능

2-2. 매개변수의 개수가 다를 때

매개변수의 갯수가 다를 때도 마찬가지 입니다.

func1은 func2보다 매개변수 타입이 적은 서브 타입이므로 다운캐스팅 가능하며, 반대로 func2는 func1보다 매개변수 갯수가 적기 때문에 func1의 슈퍼타입 이므로 업캐스팅이 불가합니다.

typescript
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가 있고 모든 매개변수 타입이 Number라고 가정했을 때, 함수 오버로딩을 통해 아래와 같이 2가지 버전의 함수를 만들 수 있습니다.

  1. 매개변수 갯수가 1개 → 매개변수의 값을 20배로 반환
  2. 매개변수 갯수가 3개 → 매개변수를 모두 더한 값을 반환

함수 오버로드 시그니처

TS에서 함수 오버로딩을 구현하려면 먼저, 함수 버전들을 미리 알려주어야 합니다.

아래와 같이 함수의 구현부 없이 함수 시그니처만 정의하는 것을 함수 오버로드 시그니처라고 부릅니다.

typescript
function func(a: number): void;
function func(a: number, b: number, c: number): void;

그럼 위 코드는 func 함수는 매개변수를 1개 받는 버전과 3개 받는 버전 총 2가지 버전이 있다고 알리는 것과 같습니다.

구현 시그니처

위와 같이 오버로드 시그니처를 만들었다면 다음으로는 구현 시그니처 (실제 구현부) 를 만들어주어야 합니다.

typescript
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() // ❌ 오류 발생, 매개변수의 개수가 맞지 않음
func(10); // ✅ 매개변수의 개수가 맞음
func(10, 20); // ❌ 오류 발생, 매개변수의 개수가 맞지 않음
func(10, 20, 30); // ✅ 매개변수의 개수가 맞음

🎯 사용자 정의 타입 가드

📌 사용자 정의 타입가드란 참 또는 거짓을 반환하는 함수를 이용해 개발자 마음대로 타입 가드를 만들 수 있도록 도와주는 TS의 문법 입니다.

사용자 정의 타입가드가 필요한 이유

기존에 살펴본 타입가드 예시를 통해 사용자 정의 타입가드가 필요한 이유를 말씀드리겠습니다.

먼저, 아래와 같이 2개의 타입 Dog와 Cat을 정의하고 두 타입의 유니온 타입인 Animal 타입을 정의했습니다.

다음으로 매개변수로 Animal 타입의 값을 받아 동물에 따라 각각 다른 경고를 콘솔에 출력하는 함수 warning을 만들어 주었습니다.

만약 이 함수를 호출하고 인수로 Dog 타입의 객체를 전달하면 "짖습니다" 또는 "안짖어요"를 출력할 것이고 Cat 타입의 객체를 전달하면 "할큅니다" 또는 "안 할퀴어요"를 출력할 것 입니다.

typescript
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 ? "할큅니다" : "안할퀴어요");
  }
}

⚠️ 그런데 이렇게 in 연산자를 이용해 타입을 좁히는 방식은 좋지 않습니다.
예를 들어 만약 Dog 타입의 프로퍼티가 중간에 이름이 수정되거나 추가 또는 삭제될 경우에는 타입 가드가 제대로 동작하지 않을 수도 있습니다.

커스텀 타입 가드 만들기

따라서 이럴 때 다음과 같이 함수를 이용해 커스텀 타입 가드를 만들어 타입을 좁히는 것이 더 좋습니다.

typescript
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 warning1(animal: Animal) {
  if (isDog(animal)) {
    console.log(animal.isBark ? "짖습니다" : "안짖어요");
  } else if ("isScratch" in animal) {
    console.log(animal.isScratch ? "" : "")
  }
}

위 코드를 해석해보면 isDog 함수는 매개변수로 받은 객체가 Dog인지 아닌지 확인해주는 함수로, Dog 타입이면 true를 반환, 아니라면 false를 반환합니다.

animal as Dog 타입 단언을 사용하여 isBark 프로퍼티가 있는지 확인하고 있다면 true를 반환합니다.

animal is Dog 구문의 의미

여기서 처음보는 타입이 있을건데, isDog 함수 반환값 타입에 animal is Dog 와 같은 식으로 반환값 타입을 정의하면 이 함수가 true를 반환하면 조건문 내부에서는 이 값이 Dog 타입임을 보장한다는 의미가 됩니다.

💡 만약 이가 없었다면 warning1 함수에서 isDog로 전달되는 animal 매개변수 타입을 보면 Animal로 되어있었을 것 입니다.
이는 TS에서는 내가 직접 만든 함수의 반환값을 가지고는 타입을 좁혀주지 않기 때문입니다.
그래서 함수 자체를 타입 가드 역할을 하도록 만들어 준 것 입니다.