728x90
함수 타입
- 함수를 가장 잘 설명하는 방법은 [어떤 타입의 매개변수]를 받고 [어떤 타입의 결과값]을 반환하는지 설명하는 것
// 일반 함수
function func(a: number, b: number): number {
return a + b;
}
// 화살표 함수
const add = (a: number, b: number): number => a + b;
- 함수의 매개변수 타입이 지정되면 결과값은 TS가 추론한다.
// 함수의 매개변수
function introduce(name = 'summermong', tall?: number) {
console.log(`name: ${name}`);
if (typeof tall === 'number') {
console.log(`tall: ${tall + 10}`);
}
}
introduce('summermong', 165);
// introduce('summermong'); // 오류 (tall 생략하고 싶으면 tall?:number 하면 됨)
- 함수의 매개변수가 2개 들어가는 상황에서 tall을 생략하려면 tall?을 찍어 string | undefined 로 유니온 타입을 만들어준다.
- 이 때 연산을 하려고 하면 undefined 일 수도 있기 때문에 tall + 10에서 에러가 발생하므로 타입 가드로 타입을 좁혀준다.
- tall?과 같이 선택적 매개변수는 필수 매개변수 앞에 올 수 없다.
// 나머지 매개변수
function getSum(...rest: number[]) { // 튜플로 [number, number, number] 해도 됨
let sum = 0;
rest.forEach((it) => (sum += it));
return sum;
}
getSum(1, 2, 3);
getSum(1, 2, 3, 4, 5);
- 나머지 매개변수의 경우 rest 파라미터를 사용해 전달할 수 있다. (배열로 묶어줌)
- getSum(1, 2, 3)을 [1, 2, 3]으로 전달하기 때문에 number[] 배열로 묶어주면 되고, 파라미터 개수만큼 적어 튜플로 표현해도 된다.
함수 타입 표현식과 호출 시그니처
- 함수의 타입을 별도로 정의하는 방법
type Operation = (a: number, b: number) => number;
const add: (a: number, b: number) => number = (a, b) => a + b; // (a, b, c) 하면 안됨
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;
name: string; // 함수도 객체다
};
const add2: Operation2 = (a, b) => a + b; // (a, b, c) 하면 안됨
const sub2: Operation2 = (a, b) => a - b;
const multiply2: Operation2 = (a, b) => a * b;
const divide2: Operation2 = (a, b) => a / b;
- 호출 시그니처는 중괄호로 함수 타입을 정의하는데 속성을 적어줄 수도 있다. (함수도 객체이기 때문에)
함수 타입의 호환성
- 특정 함수 타입을 다른 함수 타입으로 취급해도 괜찮은지 판단하는 것
- 그 기준은 2가지로 반환값의 타입이 호환되는지, 매개변수의 타입이 호환되는지다.
// 1. 반환값이 호환되는가
type A = () => number;
type B = () => 10;
let a: A = () => 10; // 반환값 타입 number
let b: B = () => 10; // 반환값 타입 number literal 10
a = b;
// b = a; (다운 캐스팅되면 호환 불가)
- a = b처럼 업 캐스팅 되는 경우는 가능하지만 b= a처럼 반환값이 다운 캐스팅 되는 경우 호환이 불가하다.
// 2-1. 매개변수가 호환되는가 (개수 동일)
type C = (value: number) => void;
type D = (value: 10) => void;
let c: C = (value) => {};
let d: D = (value) => {};
// 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; (Animal이 더 큼)
dogFunc = animalFunc;
let testFunc = (animal: Animal) => {
console.log(animal.name);
// console.log(animal.color);
}
let testFunc2 = (dog: Dog) => {
console.log(dog.name);
}
- animalFunc = dogFunc를 할당하면 Animal 타입이 더 크기 때문에 할당이 불가하다. (dogFunc의 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; // 2개에서 1개로
// func2 = func1; // 1개에서 2개로
- func1 = func2의 경우 2개에서 1개가 되는 거지만, 반대의 경우에는 1개에서 2개가 되다 보니 할당할 수 없게 된다.
- 단 이 경우는 매개변수의 개수는 달라도 타입이 같다는 전제 하에 가능하다.
함수 오버로딩
- C, JAVA처럼 함수를 매개변수의 개수나 타입에 따라 여러 가지 버전으로 정의하는 방법 (JS 지원 X)
- 동명의 함수를 가지면서 서로 다른 매개변수의 조합에 대해 여러 시그니처를 제공해 다양한 형태의 입력을 처리할 수 있게 한다.
- 따라서 각 시그니처에 맞는 타입을 지정해 타입 안정성을 확보할 수 있고, 다양한 호출 패턴을 하나의 함수 이름으로 그룹화 한다.
- 즉 다양한 매개변수 조합 처리와 타입 안정성 확보, 가독성 향상을 위해 사용한다.
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();
func(1);
// func(1, 2);
func(1, 2, 3);
- 함수의 실제 구현부로 구현 시그니처라고 한다.
- 오버로드 시그니처를 갖고 있으면, 이 함수를 호출할 때 인수의 타입과 개수가 실제 구현부가 아니라 시그니처 타입 중 하나를 따르게 된다.
- 따라서 func(), func(1, 2)는 오버로드 시그니처와 일치하지 않으므로 에러가 발생하게 된다.
- 구현 시그니처에 현재 3가지 인수를 필수로 적어놨다면, 오버로드 시그니처 1번째의 존재 의미가 없어지므로 b와 c에 옵셔널 체이닝을 사용한다.
- 그렇기 때문에 타입 가드로 조건문을 작성해줘야 한다.
사용자 정의 타입 가드
- 누군가가 만들어 놓은 객체 타입이 있고, 그렇기 때문에 서로소 유니언으로 만들 수 없다고 가정한다.
- (원래는 tag: 'Dog', tag: 'Cat'과 같이 타입을 묶어주면 된다.)
type Dog = {
name: string,
isBark: boolean
}
type Cat = {
name: string,
isScratch: boolean
}
type Animal = Dog | Cat;
- 이 경우 isBark in animal과 같이 가독성이 떨어지는 코드를 작성해야 하는데, 이럴 때 사용자 정의 타입 가드를 사용하면 좋다.
function isDog(animal: Animal):animal is Dog {
// return animal.isBark !== undefined
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;
} else if (isCat(animal)) {
// 고양이
animal;
}
}
- return animal.isBark !== undefined 이렇게 작성하면 TS 컴파일러가 잘 못 알아듣는다.
- 단순히 undefined 여부만 검사하기 때문에, Dog 타입이 isBark 속성을 가짐을 명시하는 쪽으로 수정해야 한다.
- 이 때 소괄호와 함께 animal 매개변수를 Dog 타입으로 단언하고 isBark 속성을 가진다고 알려주면 된다.
**출처: 한 입 크기로 잘라먹는 타입스크립트 (인프런, 이정환 강사님)
728x90
'❔ TypeScript > 💭 한 입 크기 TypeScript' 카테고리의 다른 글
[TypeScript] 타입스크립트의 클래스 (0) | 2023.12.21 |
---|---|
[TypeScript] 인터페이스 (0) | 2023.12.20 |
[TypeScript] 타입스크립트 이해하기 (1) | 2023.12.18 |
[TypeScript] 타입스크립트의 기본 타입 정리 (0) | 2023.12.16 |
[TypeScript] 타입스크립트 실행 및 컴파일러 옵션 설정 (0) | 2023.09.08 |