함수 타입
아래 코드에는 함수 선언식, 화살표 함수에 타입을 정의하는 방법, 그리고 함수의 매개변수에 타입을 정의하는 방법이 나와있다.
특히, 함수의 매개변수에 rest parameter를 사용하는 예도 있으니 참고하면 좋을 것 같다.
/**
* 함수 타입 정의
*/
// 함수를 설명하는 가장 좋은 방법
// 어떤 타입의 매개변수를 받고, 어떤 타입의 결과값을 반환하는지 설명
function func(a: number, b: number): number {
return a + b;
}
/**
* 화살표 함수의 타입을 정의하는 방법
*/
const add = (a: number, b: number): number => a + b;
/**
* 함수의 매개변수
*/
function introduce(name = '이정환', height?: number): void {
console.log(`name: ${name}`);
if (typeof height === 'number') {
console.log(`height: ${height}`);
}
}
introduce('이정환', 175);
introduce('이정환');
// rest parameter
function getSum1(...rest: number[]) {
let sum = 0;
rest.forEach((it) => sum += it);
return sum;
}
function getSum2(...rest: [number, number, number]) {
let sum = 0;
rest.forEach((it) => sum += it);
return sum;
}
getSum1(1, 2, 3); // 6
getSum1(1, 2, 3, 4); // 10
함수의 매개변수에는 선택적 매개변수를 넣어줄 수가 있는데, 선택적 매개변수는 필수 매개변수 앞에 위치하면 안 된다.
함수 타입 표현식과 호출 시그니처
함수의 매개변수와 반환 타입을 정의해주는 경우 다음과 같이 함수 타입 표현식과 호출 시그니처로 나타낼 수 있다.
/**
* 함수 타입 표현식
*/
type Operation = (a: number, b: number) => number;
const add1: (a: number, b: number) => number = (a, b) => a + b;
const sub1: Operation = (a, b) => a - b;
const mul1: Operation = (a, b) => a * b;
const div1: 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 mul2: Operation2 = (a, b) => a * b;
const div2: Operation2 = (a, b) => a / b;
함수 타입의 호환성
함수 타입의 호환성을 판단하는 경우에는 두 가지 사항을 고려해야 한다.
함수의 반환값을 기준으로 호환되는지 판단하는 경우는, 일반적인 경우처럼 업 캐스팅은 허용되고, 다운 캐스팅은 허용되지 않는다.
하지만, 함수의 매개변수를 기준으로 호환되는지 판단하는 경우는 다운 캐스팅은 허용되는데 업 캐스팅은 허용되지 않는다.
/**
* 함수 타입 호환성
* 특정 함수 타입을 다른 함수 타입으로 취급해도 괜찮은가를 판단하는 것
* 1. 반환값의 타입이 호환되는가
* 2. 매개변수의 타입이 호환되는가
*/
// 기준 1. 반환값이 호환되는가
type A = () => number;
type B = () => 10;
let a: A = () => 10;
let b: B = () => 10;
// 업 캐스팅
a = b;
// 다운 캐스팅
b = a; // 'A' 형식은 'B' 형식에 할당할 수 없습니다.
// 'number' 형식은 '10' 형식에 할당할 수 없습니다.
// 기준 2. 매개변수가 호환되는가
// 2-1. 매개변수의 개수가 같을 때
type C = (value: number) => void;
type D = (value: 10) => void;
let c: C = (value) => { };
let d: D = (value) => { };
// 업 캐스팅
c = d; // 'D' 형식은 'C' 형식에 할당할 수 없습니다.
// 'value' 및 'value' 매개 변수의 형식이 호환되지 않습니다.
// 'number' 형식은 '10' 형식에 할당할 수 없습니다.
// 다운 캐스팅
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; // '(dog: Dog) => void' 형식은 '(animal: Animal) => void' 형식에 할당할 수 없습니다.
// 'dog' 및 'animal' 매개 변수의 형식이 호환되지 않습니다.
// 'color' 속성이 'Animal' 형식에 없지만 'Dog' 형식에서 필수입니다.
// let testFunc1 = (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; // 'Func1' 형식은 'Func2' 형식에 할당할 수 없습니다.
// Target signature provides too few arguments.
// Expected 2 or more, but got 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);
} else {
console.log(a * 20);
}
}
func(1);
func(1, 2, 3);
사용자 정의 타입 가드
/**
* 사용자 정의 타입 가드
*/
type Dog = {
name: string;
isBark: boolean;
}
type Cat = {
name: string;
isScratch: boolean;
}
type Animal = Dog | Cat;
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 warning(animal: Animal) {
if (isDog(animal)) {
animal; // Dog 타입
} else if (isCat(animal)) {
animal; // Cat 타입
}
}
warning 함수를 보면 매개변수로 Animal 타입을 받는다.
Dog 타입인 경우와 Cat 타입인 경우 동작을 다르게 수행하고 싶을 수 있는데, 이때 사용자 정의 타입 가드를 사용하면 가독성 있게 코드를 구현해볼 수 있다.
isDog 함수와 isCat 함수가 그 예이다.
각 함수는 Animal 타입의 인수를 받아서 타입 좁히기를 수행하는 함수이다.
반환값 타입을 보면 animal is Dog/Cat이라고 되어있는데 animal 타입이 타입 좁히기를 통해 Dog/Cat 타입인지를 판단해서 반환값 타입을 정한다는 것이다.
함수 내부 구현문에 대해서 설명해보자면, 인수로 받은 animal에 isBark라는 속성이 있다면 Dog 타입, animal에 isScratch라는 속성이 있다면 Cat 타입이 될 것이다.
그런데, Cat 타입에는 isBark 속성이 없고, Dog 타입에는 isScratch 속성이 없기 때문에 animal.isBark나 animal.isScratch를 써주게 되면 에러가 발생한다.
따라서 타입 단언을 사용해서 (animal as Dog).isBark, (animal as Cat).isScratch로 나타내면 이 문제를 해결할 수 있다.
해당 타입이 undefined인지 아닌지를 판단해서 반환값 타입을 정하면 된다.
그래서, Dog 타입인 경우는 Animal -> Dog로, Cat 타입인 경우에는 Animal -> Cat으로 타입 좁히기가 잘 수행된다.
'Frontend > Typescript' 카테고리의 다른 글
제네릭 (0) | 2023.06.20 |
---|---|
인터페이스와 클래스 (0) | 2023.06.20 |
타입스크립트 이해하기 (0) | 2023.06.20 |
타입스크립트 기본 (0) | 2023.06.20 |
타입스크립트 개론 (0) | 2023.06.19 |