01
함수 타입 정의
타입스크립트에서 함수 타입을 정의하려면 함수가 받는 매개변수의 타입과 반환값의 타입을 정의해 주면 돼요.
예를 들어, 두 개의 숫자를 더하는 함수는 다음처럼 정의할 수 있어요.
여기서 반환값의 타입은 명시적으로 직접 지정해주거나 타입스크립트가 자동으로 추론해서 number로 지정해 줍니다.
function add(a: number, b: number) {
return a + b;
}
const add = (a: number, b: number): number => a + b;
✅ 기본값
함수의 매개변수에 기본값을 설정할 수 있어요. 기본값을 설정하면, 인자가 주어지지 않았을 때 기본값이 사용돼요.
function greet(name: string = "Guest") {
console.log("Hello, " + name);
}
이 함수에서 name이 없으면 "Guest"라는 기본값이 사용됩니다.
✅ 선택적 매개변수:?
선택적 매개변수는?를 사용해서 정의합니다. 선택적 매개변수는 인자가 주어지지 않아도 에러가 나지 않아요.
function greet(name?: string) {
console.log("Hello, " + (name || "Guest"));
}
단, 필수 매개변수와 선택적 매개변수를 같이 사용하는 경우에는 필수 매개변수는 선택적 매개변수 앞에 위치해야 합니다.
// ✅
function greet(id: number, name?: string) {
console.log(`Hello, ${(name || "Guest"} : ${id} `);
}
// ❌
function greet(name?: string, id: number) {
console.log(`Hello, ${(name || "Guest"} : ${id} `);
}
✅ rest parameter
... 을 사용하면 rest parameter라고 해서 여러 개의 값을 배열로 받을 수 있어요.
function sum(...numbers: number[]) {
return numbers.reduce((acc, num) => acc + num, 0);
}
이 함수는 전달된 모든 숫자를 더하는 함수예요. rest 파라미터 덕분에 몇 개의 숫자가 들어오든 처리할 수 있습니다.
02
함수 타입 표현식 (Function Type Expression)
함수 타입 표현식은 여러 개의 함수가 동일한 타입을 가질 때 유용합니다.
예를 들어, 덧셈, 뺄셈, 곱셈, 나눗셈 함수를 만들 때 타입을 반복해서 정의해야 할 때,
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;
이렇게 타입을 정의하면 같은 타입을 가진 함수들이 많아질 때 유용하죠.
03
호출 시그니쳐 (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;
03
함수 타입의 호환성
타입스크립트는 함수 타입이 호환되는지 체크할 수 있습니다.
즉, 하나의 함수가 다른 함수로 대체 가능한지 판단해 주는 거죠. 함수 타입의 호환성은 크게 두 가지로 나눠져요.
1. 반환값 타입 호환성
두 함수의 반환값 타입이 호환되는지 체크해요.
예를 들어, 한 함수의 반환값 타입이 number인 경우, 다른 함수의 반환값도 number여야 호환됩니다.
다만, 반환값이 더 좁은 타입에서 더 넓은 타입으로는 호환되지 않아요.
type A = () => number;
type B = () => 10;
let a: A = () => 10;
let b: B = () => 10;
a = b; // ✅
b = a; // ❌
코드를 보면 업캐스팅은 가능하지만 다운캐스팅은 안되는 걸 볼 수 있습니다.
2. 매개변수 타입 호환성
두 번째 기준인 매개변수의 타입이 호환되는지 판단할 때에는
두 함수의 매개변수의 개수가 같은지 다른지에 따라 두 가지 유형으로 나뉘게 됩니다.
✅ 매개변수의 개수가 같을 때
매개변수의 타입이 더 좁거나 구체적인 함수는 더 넓은 타입을 받는 함수와 호환되지 않아요.
반대로, 매개변수의 타입이 더 넓은 함수는 더 좁은 타입을 받는 함수와 호환됩니다.
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를 코드로 표현해 보면 다음과 같습니다.
let animalFunc = (animal: Animal) => {
console.log(animal.name); // ✅
console.log(animal.color); // ❌
};
animalFunc 타입의 매개변수 타입은 Animal 타입입니다.
그러나 dogFunc 함수 내부에서는 name과 color 프로퍼티에 접근합니다.
따라서 이렇게 할당이 이루어지게 되면 animal.color처럼 존재할 거라고 보장할 수 없는 프로퍼티에 접근하게 됩니다.
반대로 dogFunc = animalFunc를 코드로 표현하면 다음과 같습니다.
let dogFunc = (dog: Dog) => {
console.log(dog.name);
};
dogFunc 함수의 매개변수는 Dog 타입입니다.
그리고 animalFunc 함수 내부에서는 name 프로퍼티에만 접근합니다. 그래서 이 코드는 안전합니다.
✅ 매개변수의 개수가 다를 때
함수의 매개변수 개수가 다를 때, 매개변수가 적은 함수는 매개변수가 많은 함수와 호환될 수 있습니다.
하지만, 반대로 매개변수가 많은 함수는 매개변수가 적은 함수와 호환되지 않아요.
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는 허용돼요. 이유는 func2가 a 하나만 받는데, func1은 a, b를 받죠.
func1이 더 많은 매개변수를 받을 수 있으니까 func2와 호환됩니다.
반대로, func2 = func1은 허용되지 않아요. func1은 a와 b를 받지만,
func2는 a만 받으니까 func2가 func1을 대체할 수 없어요.
04
함수 오버로딩
함수 오버로딩은 하나의 함수가 여러 가지 버전으로 정의되는 방식이에요.
즉, 매개변수의 개수나 타입에 따라서 함수가 다르게 동작하도록 만들 수 있는 방법입니다.
하지만 자바스크립트에서는 지원되지 않으며, 타입스크립트에서만 지원되는 기능이에요.
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); // 3개의 숫자가 있을 경우
} else {
console.log(a * 20); // 1개의 숫자만 있을 경우
}
}
func(1); // ✅ 1개 인자 사용 - 첫 번째 버전 호출
func(1, 2); // ❌ 두 번째 버전 호출 안됨 (타입 에러)
func(1, 2, 3); // ✅ 3개 인자 사용 - 세 번째 버전 호출
📂 설명
- 오버로드 시그니처 정의:
👉 첫 번째는 func(a: number)로, a만 받는 함수의 타입을 정의한 거예요.
👉 두 번째는 func(a: number, b: number, c: number)로, a, b, c를 받는 함수의 타입을 정의한 거죠. - 구현부 작성:
👉 실제 함수 구현에서는 b와 c가 있을 때와 없을 때로 나누어 처리할 수 있어요.
👉 b와 c가 모두 숫자일 때는 세 개의 값을 더하고, 하나만 있을 때는 1개의 값만 곱해서 계산하는 방식으로 구현합니다.
2. 왜 함수 오버로딩을 사용할까?
함수 오버로딩을 사용하면 같은 함수 이름으로 여러 버전을 만들 수 있어요.
매개변수의 개수나 타입에 따라 함수가 다르게 동작하게 할 수 있기 때문에, 코드가 더 깔끔하고 재사용하기 쉬워집니다.
예를 들어, 숫자 두 개를 더하는 함수가 필요하다면, 숫자 세 개를 더하는 버전을 같은 함수 이름으로 만들 수 있어요.
✅ 유의사항
: 오버로드 시그니처는 함수 구현부보다 위에 정의해야 합니다.
실제 구현부에서는 시그니처와 다르게 구현할 수 있지만, 매개변수에 따라 조건을 나누어 처리해야 합니다.
05
사용자 정의 타입가드
사용자 정의 타입가드는 참 또는 거짓을 반환하는 함수를 이용해서 우리가 직접 타입을 확인하는 방법이에요.
이 방법을 사용하면 타입스크립트가 더 정확하게 타입을 구분할 수 있게 도와줍니다.
예를 들어, Dog과 Cat 타입이 있을 때, isDog와 isCat 함수를 만들어 각각의 타입을 확인할 수 있어요.
type Dog = {
name: string;
isBark: boolean;
};
type Cat = {
name: string;
isScratch: boolean;
};
type Animal = Dog | Cat;
// 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) {
// isDog 함수를 사용해서 animal이 Dog인지 확인
if (isDog(animal)) {
console.log(animal.isBark ? "짖습니다" : "안짖어요");
}
// 그 외는 Cat 타입
else if (isCat(animal)) {
console.log(animal.isScratch ? "할큅니다" : "안할퀴어요");
}
}
📂 설명
- 타입가드 함수는 isDog, isCat처럼 작성됩니다. 이 함수는 해당 객체가 특정 타입인지 확인하고 참/거짓을 반환합니다.
👉 isDog(animal)은 animal이 Dog 타입일 때만 true를 반환합니다.
👉 isCat(animal)은 animal이 Cat 타입일 때만 true를 반환합니다. - warning 함수에서 isDog를 사용하면, animal이 Dog 타입인지를 확인하고, 그 타입에 맞는 동작을 합니다.
이때 타입이 좁혀져서 animal의 타입을 정확하게 인식할 수 있습니다. - animal is Dog와 같은 타입의 반환은 타입스크립트에게 animal이 Dog 타입이라고 보장해 줍니다.
그래서 타입이 확정된 후, isDog가 true를 반환하는 조건문 내부에서만 Dog 타입에 접근할 수 있습니다.
" is의 의미 "
is는 타입가드 함수에서 중요한 역할을 합니다. TypeScript에서 is는 타입을 좁히는 역할을 합니다.
즉, is는 TypeScript에게 "이 타입이 맞다는 걸 보장할 때" 사용되고,
그 결과로 해당 변수의 타입을 명확히 좁혀서 더 안전하게 사용할 수 있게 도와줍니다.
06
마무리
오늘은 TypeScript에서 함수 타입, 함수 오버로딩, 사용자 정의 타입가드에 대해 알아봤습니다.
이 기능들을 잘 활용하면, 우리가 다루는 데이터의 타입을 더 정확하게 정의하고 관리할 수 있어요.
- 함수 타입 표현식을 정의함으로써 함수의 타입을 명확하게 설정하고 재사용성을 높일 수 있습니다.
- 호출시그니처는 함수 타입을 객체 형식으로 정의하여, 다른 속성을 추가하고 타입을 명확히 설정할 수 있게 도와준다.
- 함수 타입 정의를 통해 매개변수랑 반환값의 타입을 확실히 설정할 수 있습니다.
- 함수 오버로딩으로 하나의 함수로 여러 가지 버전의 처리를 할 수 있죠.
- 사용자 정의 타입가드는 객체가 어떤 타입인지 정확하게 체크해 줘서 코드가 더 안전해집니다.
이렇게 타입을 명확하게 지정하면, 코드에서 실수를 줄일 수 있고 유지보수도 쉬워집니다!!
'DEVELOPMENT > Typescript' 카테고리의 다른 글
[TS] 클래스 (class) (1) | 2025.03.01 |
---|---|
[TS] 인터페이스 (Interface) (0) | 2025.02.28 |
[TS] 타입 시스템 (0) | 2025.02.26 |
[TS] 타입 계층과 변환 개념 (업캐스팅, 다운캐스팅, unknown, never) (0) | 2025.02.25 |
[TS] 타입스크립트 모든 타입 한 번에 정리 (0) | 2025.02.25 |