반응형
함수 타입
함수의 타입을 정의하는 방법
- 다음과 같은 자바스크립트 함수가 있을 때, 이 함수를 다른 사람에게 설명하는 가장 좋은 방법은
이 함수가 어떤 매개변수를 받고 어떤 값을 반환 하는지 이야기 하는 것 이다.
// 함수를 설명하는 가장 좋은 방법
// 어떤 매개변수를 받고, 어떤 결과값을 반환하는지 설명
function func(a, b) {
return a + b;
}
- TS 에서 함수 정의하는 방법
// 매개변수의 타입과 반환 값의 타입을 넣어주면 된다.
// 함수의 반환 값은 자동으로 추론되어 생략이 가능하다.
function func(a: number, b: number): number {
return a + b;
}
- 화살표 함수 타입 정의 방법
const add = (a: number, b: number): number => a + b;
매개변수 기본값 설정하기
// 다음과 같이 함수의 매개변수에 기본값이 설정되어 있으면 타입이 자동으로 추론된다.
function introduce(name = "이재민") {
console.log(`name : ${name}`);
}
// 이 때 기본값과 다른 타입으로 매개변수의 타입을 정의하면 오류가 발생함
function introduce(name:number = "이재민") {
console.log(`name : ${name}`);
}
// 당연히 다른 타입의 값을 인수로 전달해도 오류가 발생한다.
function introduce(name = "이재민") {
console.log(`name : ${name}`);
}
introduce(1); // 오류
선택적 매개변수 설정하기
- 매개변수의 이름뒤에 ? 를 붙여주면 선택적 매개변수가 되어 생략이 가능하다.
function introduce(name = "이재민", tall?: number) {
console.log(`name : ${name}`);
console.log(`tall : ${tall}`);
}
introduce("이재민", 174);
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}`);
}
}
- 한 가지 주의할 점은 선택적 매개변수는 필수 매개변수 앞에 올 수 없다.
- 반드시 뒤에 배치해야한다.
function introduce(name = "이재민", tall?: number, age: number) {
// 오류!
console.log(`name : ${name}`);
if (typeof tall === "number") {
console.log(`tall : ${tall + 10}`);
}
}
나머지 매개변수
다음과 같이 여러개의 숫자를 인수로 받는 함수가 있다고 가정
function getSum(...rest) {
let sum = 0;
rest.forEach((it) => (sum += it));
return sum;
}
- getSum 함수는 나머지 매개변수 rest로 배열 형태로 number 타입의 인수들을 담은 배열을 전달받는다.
- 이때 rest 파라미터의 타입은 다음과 같이 정의하면 된다.
function getSum(...rest: number[]) {
let sum = 0;
rest.forEach((it) => (sum += it));
return sum;
}
- 만약 나머지 매개변수의 길이를 고정하고 싶다면 다음과 같이 튜플 타입을 이용해도 된다.
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) // ❌
함수 타입 표현식
함수 타입을 타입 별칭과 함께 별도로 정의할 수 있다. 이를 함수 표현식이라고 부른다.
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;
호출 시그니쳐
호출 시그니처는 함수 타입 표현식과 동일하게 함수의 타입을 별도로 정의하는 방식이다.
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;
- JS 에서는 함수도 객체이기 때문에, 위 코드 처럼 객체를 정의하듯 함수의 타입을 별도로 정의할 수 있다.
- 참고로 이때 다음과 같이 호출 시그니처 아래에 프로퍼티를 추가 정의하는 것도 가능하다.
- 이럴 경우 함수이자 일반 객체를 의미하는 타입으로 정의되며 이를 하이브리드 타입이라고 부른다.
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; // ❌
기준 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 매개변수 타입보다 작은 서브타입이기 때문
- 반대로는 가능하다. 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 프로퍼티에만 접근한다.
- 그러므로 두개의 함수 타입 C와 D가 있을 때 두 매개변수의 개수가 같을 경우 D를 C로 취급하려면
C 매개변수의 타입이 D 매개변수 타입의 서브 타입이어야 한다.
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; // ❌
반응형
'Typescript' 카테고리의 다른 글
Typescript | 인터페이스, 확장, 선언 합치기 (0) | 2024.11.02 |
---|---|
Typescript | 함수 오버로딩, 사용자 정의 타입가드 (0) | 2024.11.02 |
Typescript | 타입 추론, 타입 단언, 타입 좁히기, 서로소 유니온 타입 (0) | 2024.10.29 |
Typescript | 타입 호환성, 대수타입 (2) | 2024.10.29 |
Typescript | void, never (0) | 2024.10.25 |