함수의 타입을 정의하는 방법
자바스크립트 : 어떤 매개변수를 받고, 어떤 결과값을 반환하는지 설명 타입스크립트 : 어떤 타입의 매개변수를 받고, 어떤 타입의 값을 반환하는지 설명
// 함수를 설명하는 가장 좋은 방법
// 어떤 매개변수를 받고, 어떤 결과값을 반환하는지 설명
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 : 반환값 타입이 호환되는가?
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 타입인지 확인하고 타입을 좁힐 수 있다.
'Frontend > TypeScript' 카테고리의 다른 글
[TypeScript] 5. 클래스 (0) | 2025.01.27 |
---|---|
[TypeScript] 4. 인터페이스 (0) | 2025.01.26 |
[TypeScript] 2-2. 타입스크립트 이해하기 - 추론과 단언 (0) | 2025.01.24 |
[TypeScript] 2-1. 타입스크립트 이해하기 - 타입 관계 (0) | 2025.01.23 |
[TypeScript] 1. 타입스크립트 기본 (1) | 2025.01.22 |