1. 함수의 타입을 정의하는 방법
// 함수를 설명하는 가장 좋은 방법
// 어떤 매개변수를 받고, 어떤 결과값을 반환하는지 설명
function func(a, b) {
return a + b;
}
function func(a: number, b: number): number {
return a + b;
}
// 생략가능
function func(a: number, b: number) {
return a + b;
}
- 타입스크립트의 함수는 어떤 타입의 매개변수를 받고 어떤 타입의 값을 반환하는지 정의해주면 된다.
- 반환값 타입은 자동으로 추론되기 때문에 생략이 가능하다.
1.1 화살표 함수 타입
const add = (a: number, b: number): number => a + b;
// 생략 가능
const add = (a: number, b: number) => a + b;
- 함수 선언식과 같이 타입을 정의하고 반환값 타입은 자동으로 추론된다.
1.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); // 오류
- 함수의 매개변수에 기본값이 설정되어 있으면 자동으로 타입이 추론되어 생략가능하다.
- 단, 기본값과 다른 타입으로 매개변수의 타입의 정의하면 오류가 발생한다.
- 기본값과 다른 타입의 값을 인수로 전달해도 오류가 발생한다.
1.3 선택적 매개변수 설정하기
function introduce(name = "이정환", tall?: number) {
console.log(`name : ${name}`);
console.log(`tall : ${tall}`);
}
introduce("이정환", 156);
introduce("이정환");
- 매개변수 이름뒤에 ? 를 붙여주면 선택적 매개변수가 되어 생략이 가능하다.
function introduce(name = "이정환", tall?: number) {
console.log(`name : ${name}`);
if (typeof tall === "number") {
console.log(`tall : ${tall + 10}`);
}
}
- tall 같은 선택적 매개변수의 타입은 자동으로 undefined와 유니온 타입으로 추론된다.
- tall의 현재 타입은 number | undefined 이다.
- tall 값을 number 타입으로 사용하기 위해서는 타입 좁히기가 필요하다. → 조건문 사용
function introduce(name = "이정환", tall?: number, age: number) { // 오류!
console.log(`name : ${name}`);
if (typeof tall === "number") {
console.log(`tall : ${tall + 10}`);
}
}
- 선택적 매개변수의 타입을 사용할 때 주의할 점은 필수 매개변수 앞에 올 수 없다.
- 반드시 맨 뒤에 배치를 해야한다.
1.4 나머지 매개변수
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) // ❌
- 여러개의 숫자를 인수로 받는 getSum 함수는 나머지 매개변수 rest 배열 형태로 number 타입의 인수들을 담은 배열을 전달 받는다.
- 만약 나머지 매개변수의 길이를 고정하고 싶다면 튜플 타입을 이용할 수 있다.
2. 함수 타입 표현식
type Add = (a: number, b: number) => number;
const add: Add = (a, b) => a + b;
- 함수 타입을 타입 별칭과 함께 별도로 정의할 수 있다. 이를 함수 타입 표현식이라고 한다.
- 함수 타입 표현식을 사용하면 함수 선언 및 구현 코드와 타입 선언을 분리할 수 있어 유용하다.
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;
- 여러개의 함수가 동일한 타입을 갖는 경우 간결하게 만들 수 있고 동일한 타입의 함수가 추가 되어도 타입 주석을 이용해 타입만 정의해주면 되서 유용하다.
const add: (a: number, b: number) => number = (a, b) => a + b;
- 타입 주석에 함수 타입 표현식 사용 가능하다.
3. 호출 시그니쳐
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);
add.name;
- 호출 시그니쳐 아래 추가 정의를 하는것도 가능하다.
- 이는 함수이자 일반 객체를 의미하는 타입으로 정의되며 하이브리드 타입이라고 부른다.
4. 함수 타입의 호환성
4.1 함수타입의 호환성이란?
함수 타입의 호환성이란 특정 함수 타입을 다른 함수 타입으로 호환할 수 있는지 판단 하는것이다.
함수 타입의 호환성은 두 가지를 기준으로 판단한다.
- 두 함수의 반환값 타입이 호환되는가?
- 두 함수의 매개변수의 타입이 호환되는가?
4.2 반환값 타입이 호환되는가?
type A = () => number;
type B = () => 10;
let a: A = () => 10;
let b: B = () => 10;
a = b; // ✅
b = a; // ❌
- A 반환값 타입이 B 반환값 타입의 슈퍼타입이라면 두 타입은 호환된다.
4.3 매개변수의 타입이 호환되는가?
매개변수의 타입이 호환되는지 판단 할 때에는 두가지 유형으로 나뉘게 된다.
1) 매개변수의 개수가 같을 때
type C = (value: number) => void;
type D = (value: 10) => void;
let c: C = (value) => {};
let d: D = (value) => {};
c = d; // ❌
d = c; // ✅
- 두 타입의 매개변수의 개수가 같다면 C 매개변수의 타입이 D 매개변수 타입의 서브 타입일 때 호환된다.
- 반환값 타입과는 반대로 마치 다운캐스팅을 허용하는 것 같아 보인다.
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); // ❌
};
// dogFunc = animalFunc
let dogFunc = (dog: Dog) => {
console.log(dog.name);
};
- animalFunc 타입의 매개변수 타입은 Animal 타입이다.
- dogFunc 함수 내부에서 name과 color 프로퍼티에 접근하고 할당이 이루어지게 되면 animal.color 처럼 존재할거라고 보장할 수 없는 프로퍼티에 접근하게 된다.
- dogFunc 함수의 매개변수 타입이 Dog 타입일 때는 animalFunc 함수 내부에서 name 프로퍼티만 접근하기 때문에 안전할 수 있다.
💡 결론적으로 두개의 함수 타입 C와 D가 있을 때 두 매개변수의 개수가 같을 경우 D를 C로 취급하려면 C 매개변수의 타입이 D 매개변수 타입의 서브 타입이어야한다.
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; // ❌
5. 함수 오버로딩
- 함수 오버로딩이란 하나의 함수를 매개변수의 개수나 타입에 따라 다르게 동작하는 문법이다.
- 즉, 같은 함수를 매개변수의 개수나 타입에 따라 여러가지 버전으로 만든다.
// 버전들 -> 오버로드 시그니쳐
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 - 오버로드 시그니쳐
- 타입스크립트의 함수 오버로딩을 구현하려면 버전별 오버로드 시그니쳐를 만들어 줘야한다.
- 위와같이 구현부 없이 선언부만 만들어둔 함수를 오버로드 시그니쳐 라고한다.
- func 함수는 매개변수를 1개 받는 버전과 3개 받는 버전 총 두개의 버전이 있다.
- 오버로드 시그니쳐를 만든 다음에는 구현 시그니쳐를 만든다.
- 실제로 함수가 어떻게 실행될 것인지 정의하는 부분을 구현 시그니쳐 라고 한다.
- 구현 시그니쳐의 매개변수 타입은 모든 오버로드 시그니쳐와 호환되도록 만들어야 한다. → 선택적 매개변수 사용
6. 사용자 정의 타입가드
사용자 정의 타입가드란 참 또는 거짓을 반환하는 함수를 이용해 타입 가드를 만들 수 있도록 도와주는 문법이다.
type Dog = {
name: string;
isBark: boolean;
};
type Cat = {
name: string;
isScratch: boolean;
};
// 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 ? "할큅니다" : "안할퀴어요");
}
}
- animal is Dog는 이 함수가 true를 반환하면 조건문 내부에서는 이 값이 Dog 타입을 보장한다는 의미이다.
- 이처럼 warnimg 함수에서 isDog 함수를 호출해 매개변수의 값이 Dog 타입인지 확인하고 타입 좁히기를 할 수 있다.
📌
'Front-end > TypeScript' 카테고리의 다른 글
[TS] 타입 조작하기 (0) | 2024.02.22 |
---|---|
[TS] 제네릭에 대하여 (0) | 2024.02.21 |
[TS] 타입을 추론하고... 단언하고... 좁히고... (0) | 2024.02.19 |
[TS] 타입 별칭(type) vs 인터페이스(interface) (1) | 2024.02.18 |
[TS] 타입은 집합이다? (1) | 2024.02.17 |