석이의 개발일지
FUNCTIONS 본문
728x90
Call Signatures
- 프로퍼티로 호출 가능한 것을 설명하려면 객체 타입에 Call Signature을 작성할 수 있다.
- Call Signatures는 다음과 같이 함수의 매개 변수(parameter)와 반환 타입을 지정한다.
- Call(=Function) Signature 란 함수의 매개 변수와 반환 값의 타입을 모두 type으로 미리 선언하는 것
- React에서 함수로 props를 보낼 때, 어떻게 작동할지 미리 설계 가능하다.
type PizzaFunction = {
pizza: string;
(args: number): boolean;
};
function hello(fn: PizzaFunction) {
console.log(fn.pizza, fn(6));
}
type Add = {
(a: number, b: number): number;
}
// type Add = (a: number, b: number) => number;
const add: Add = (a, b) => a + b
궁금한 점
type Add = (a:number, b:number) => number
const add:Add = (a + b) => a + b // ✅
const add:Add = (a + b) => {a + b} // ❌ error : void 를 반환하지 않는다. number를 반환해야 한다.
- {}를 사용하면 그 값이 반환값이 함수 내부의 내용으로 처리가 된다.
예시
// 1
const add:Add = (a,b) => a+b 를 함수로 풀면 다음과 같게 됩니다.
function add(a, b) {
return (a+b)
// 2
const add:Add = (a,b) => {a+b} 를 함수로 풀면 다음과 같게 됩니다.
function add(a, b) {
a+b;
- 즉 애로우 함수에 {}를 사용하게 되면 그 안의 값은 반환이 아니라 함수 내부 내용으로 처리되기에 반환값이 없는 void로 처리된다.
이에 따라 위에서 미리 선언한 Add 자료형의 반환값은 number 라 고정해놓은 내용과 충돌하기에 에러가 발생한다.
Function Overloads
- 동일한 이름에 매개 변수와 매개 변수 타입 또는 리턴 타입이 다른 여러 버전의 함수를 만드는 것을 말한다.
TypeScript에서는 오버로드 signatures을 작성하여 "다양한 방식으로 호출할 수 있는 함수"를 지정할 수 있다. - Function(=Method) Overlading은 직접 작성하기보다 외부 라이브러리에 자주 보이는 형태로, 하나의 함수가 복수의 Call Signature를 가질 때 발생한다.
type Add = {
(a: number, b: number): number,
(a: number, b: string): number
}
const add: Add = (a, b) => {
if (typeof b === "string") return a;
return a + b;
}
// 매개변수의 데이터 타입이 다른 경우 예외 처리
type Add2 = {
(a: number, b: number): number,
(a: number, b: number, c: number): number
}
const add2: Add2 = (a, b, c?: number) => {
if (c) return a + b + c;
return a + b;
}
// 매개변수의 수가 다른 경우 예외 처리
- 위와 같은 함수는 거의 없지만 외부 라이브러리에서 활용될 수 있다.
router.push("/home");
router.push({
path: "/home",
state: 1
});
- 예를 들어, Next.js의 라우터 push가 대충 두가지 방법으로 페이지를 이동한다고 할 때
type Config = {
path: string,
state: number
}
type Push = {
(config: Config): void,
(config: string): void
}
const push: Push = (config) => {
if (typeof config === "string") console.log(config);
else console.log(config.path);
}
- 패키지나 라이브러리는 위와 같이 두 가지 경우의 Overloading으로 디자인되어 있을 것이다.
Polymorphism
- 인자들과 반환값에 대하여 형태(타입)에 따라 그에 상응하는 형태(타입)를 가질 수 있다.
- any와의 차임점은 해당 타입에 대한 정보를 잃지 않는다.
any는 any로서 밖에 알 수 없지만 generics는 타입 정보를 알 수 있다.
type SuperPrint = { (arr: T[]): void }
type SuperReturn = { (arr: T[]): T }
const superPrint: SuperPrint = (arr) => {
arr.forEach(i => console.log(i))
}
const superReturn: SuperReturn = (arr) => arr[0]
superPrint([1, 2, false, true])
console.log(superReturn([1, 2, 3, 4]))
Generics
- 제네릭은 C# 이나 Java와 같은 언어에서 재사용 가능한 컴포넌트를 만들기 위해 사용하는 기법이다.
단일 타입이 아닌 다양한 타입에서 작동할 수 있는 컴포넌트를 생성할 수 있다. - 구체적인 타입을 지정하지 않고 다양한 인수와 리턴 값에 대한 타입을 처리할 수 있다.
- 타입스크립트에서 제네릭을 통해 인터페이스, 함수 등의 재사용성을 높일 수 있다.
function identity< Type >(arg: Type): Type {
return arg;
}
// 제네릭 화살표 함수 (tsx기준)
const identity=< Type extends {} >(arg: Type):Type => {
return arg;
}
let output = identity< string >("myString"); // 첫 번째 방법
let output = identity("myString"); // 두 번째 방법
// 두 번째 방법은 type argument inference(타입 인수 유추)를 사용합니다. 즉, 컴파일러가 전달하는 인수 유형에 따라 자동으로 Type 값을 설정하기를 원합니다.
any를 넣는 것과 Generic의 차이는 무엇일까?
type SuperPrint = {
(arr: any[]): any
}
const superPrint: SuperPrint = (arr) => arr[0]
let a = superPrint([1, "b", true]);
// pass
a.toUpperCase();
any를 사용하면 위와 같은 경우에도 에러가 발생하지 않는다
type SuperPrint = {
(arr: T[]): T
}
const superPrint: SuperPrint = (arr) => arr[0]
let a = superPrint([1, "b", true]);
// error
a.toUpperCase();
- Generic의 경우 에러가 발생해 보호받을 수 있다.
(Call Signature를 concrete type으로 하나씩 추가하는 형태이기 때문이다.)
type SuperPrint = {
(arr: T[], x: M): T
}
const superPrint: SuperPrint = (arr, x) => arr[0]
let a = superPrint([1, "b", true], "hi");
- 위와 같이 복수의 Generic을 선언해 사용할 수 있다.
"제네릭은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법"
cmmand 하고 클릭했을 때 넘어가는 이 설명 부분에 <...> 이런 것을 붙이는 이유가 제네릭 때문이었다니...
LIST
'TypeScript' 카테고리의 다른 글
TypeScript 설정 (2) | 2023.02.20 |
---|---|
CLASSES AND INTERFACES (0) | 2023.02.12 |
OVERVIEW OF TYPESCRIPT (6) | 2023.02.09 |
Comments