타입스크립트 함수와 타입
TIL 23. 12. 18
함수 타입
타입스크립트에서 함수를 설명하는 방법으로 함수의 매개변수와 반환값에 대한 타입을 명시한다. 함수의 타입은 다음과 같이 매개 변수와 반환값의 타입으로 결정된다.
// function func(a: number, b: number): number
function add(a: number, b: number) {
return a + b;
}
// const sub: (a: number, b: number) => number
const sub = (a: number, b: number) => a - b;
함수의 반환값 타입은 자동으로 추론하기 때문에 생략할 수 있다. 매개변수에 기본값이 설정되어 있을 때도 타입이 자동으로 추론된다.
// name: string, location: string | undefined
function welcome(name = "사용자", location?: string) {
console.log(`${name}님, 안녕하세요.`)
if (typeof location === "string") {
console.log(`현재 위치는 ${location}입니다.`)
}
}
매개변수의 이름 뒤에 물음표(?)를 붙여주면 선택적 매개변수가 되어 생략 가능하다. 선택적 매개변수의 타입은 자동으로 undefined와 유니온 된 타입으로 추론된다.
함수 타입 표현식
함수 타입을 타입 별칭을 이용해서 정의할 수 있다. 이를 함수 타입 표현식이라고 부른다.
type Operation = (a: number, a: number) => number;
const add: Operation = (a, b) => a + b;
const sub: Operation = (a, b) => a - b;
함수 타입 표현식을 사용하면 함수 선언부와 타입 선언부가 분리되어 가독성이 좋아진다. 동일한 타입을 갖는 함수들이 추가되어도 타입 주석을 통해 타입 정의만 해주면 되기 때문에 편리하다.
호출 시그니쳐
호출 시그니쳐 역시 함수 타입 표현식과 동일하게 함수의 타입을 별도로 정의하는 방식이다. 함수 타입 표현식과 다르게 매개변수 타입과 반환값 타입 사이에 :
을 사용한다.
type Operation = {
(a: number, b: number): number;
}
const add: Operation: (a, b) => a + b;
const sub: Operation: (a, b) => a - b;
함수들은 호출 가능하며 프로퍼티도 가질 수 있다. 자바스크립트에서는 함수도 객체이기 때문에, 위 코드처럼 객체를 정의하듯 함수의 타입을 별도로 정의할 수 있다.
함수 타입의 호환성
함수 타입의 호환성이란 특정 함수 타입을 다른 함수 타입으로 괜찮은지 판단하는 것을 의미한다. 다음 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가 있을 때 두 매개변수의 개수가 같을 경우 D를 C로 취급하려면 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 타입 변수 c를 D 타입의 변수 d에 할당하는 것은 가능하다. Number Literal 타입의 값 10은 Number 타입에 포함된다.
두 함수의 매개변수 타입이 객체 타입일 때를 살펴보자.
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 타입의 매개변수 타입은 name 프로퍼티만을 갖는 Animal 타입이다. dogFunc 함수 내부에서는 name과 color 프로퍼티에 접근한다. 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; // ❌
함수 오버로딩
함수 오버로딩이란 하나의 함수를 매개변수의 개수나 타입에 따라 다르게 동작하도록 만드는 문법이다. 타입스크립트에서 함수 오버로딩을 구현하려면 버전별 오버로드 시그니쳐를 정의해야 한다.
function func(a: number): void;
function func(a: number, b: number, c: number): void;
구현부 없이 선언부만 만들어둔 함수를 ‘오버로드 시그니쳐’라고 한다. 첫 번째 func 함수는 매개변수를 1개를 받고 두 번째 func 함수는 매개변수 3개를 받는다.
오버로드 시그니쳐를 만들고 난 후에는 구현 시그니쳐를 만들어줘야 한다. ‘구현 시그니쳐’는 실제로 함수가 어떻게 실행될 것인지를 정의하는 부분이다.
// 버전들 -> 오버로드 시그니쳐
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 - 오버로드 시그니쳐
구현 시그니쳐의 매개변수 타입은 모든 오버로드 시그니쳐와 호환되도록 만들어야 한다.
사용자 정의 타입 가드
지금까지는 타입을 좁히기 위해 기존 자바스크립트의 구문을 사용했었다.
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 ? "할큅니다" : "안할퀴어요");
}
}
그러나 프로퍼티 이름을 기준으로 타입을 좁히는 것은 직관적으로 이해하기 쉽지 않고 프로퍼티 이름이 변경된다면 타입 가드가 제대로 동작하지 않을 수 있다.
사용자 정의 타입 가드란 참 또는 거짓을 반환하는 함수를 이용해 타입을 좁힐 수 있는 방법이다.
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)) {
console.log(animal.isBark ? "짖습니다" : "안짖어요");
} else {
console.log(animal.isScratch ? "할큅니다" : "안할퀴어요");
}
}
반환값의 타입으로 animal is Dog
를 정의하면 함수가 true를 반환한다면 animal 변수가 Dog 타입임을 보장한다. 사용자 정의 타입 가드는 전달된 매개변수가 어떠한 타입인지 확인하여 타입을 좁힌다.
댓글남기기