함수 타입은 함수의 매개변수와 반환값을 기술하며, 화살표로 씁니다: (a: number, b: number) => number. type으로 이름을 붙이면 변수, 매개변수, 객체 속성에 쓸 수 있습니다.
add에 대입한 함수에는 표기가 필요 없습니다. 매개변수 타입은 Operation에서 옵니다. 이를 문맥적 타이핑이라고 하며, 함수 타입에 이름을 붙이는 주된 이유입니다.
함수 타입 문법
모양은 (parameters) => ReturnType입니다. 각 매개변수에는 이름과 타입이 필요합니다. 이름은 가독성과 에디터 힌트를 위한 것이며, 매개변수 타입이 호환되는 함수라면 이름을 무엇으로 부르든 맞습니다.
type Predicate = (value: number) => boolean;
type Formatter = (value: number, digits?: number) => string; // optional parameter
type Logger = (...parts: string[]) => void; // rest parameter
type Factory = () => { id: number }; // no parameters
이름은 생략할 수 없습니다. (string) => void는 타입 없이 string이라는 이름의 매개변수를 선언하는 것이고, strict 모드는 TS7051: Parameter has a name but no type. Did you mean 'arg0: string'?으로 거부합니다. (value: string) => void라고 쓰세요.
화살표 함수에 타입 붙이기
화살표 함수에는 두 가지 방법으로 타입을 붙일 수 있습니다. 함수 자체에 표기하거나, 변수에 함수 타입을 표기하고 매개변수는 추론에 맡깁니다.
독립된 함수에는 보통 형태 1을 씁니다. 형태 3은 여러 함수가 시그니처 하나를 공유할 때(핸들러, 비교 함수, 변환 함수) 빛을 발합니다. 시그니처를 한 번만 쓰고, 바뀌면 모든 곳에서 검사되기 때문입니다.
콜백 타입
타입이 함수 타입인 매개변수가 콜백입니다. 호출하는 쪽의 함수는 그 타입에 대해 검사되고, 매개변수는 그 타입에서 추론됩니다.
두 가지 규칙 덕분에 콜백을 편하게 쓸 수 있습니다.
- 매개변수가 더 적어도 됩니다. 매개변수 하나를 받는 함수를 두 개가 제공되는 자리에 넘길 수 있습니다. JavaScript는 남는 인수를 무시하므로 TypeScript도 허용합니다. 타입이 제공하는 것보다 매개변수가 많으면 오류입니다(
Target signature provides too few arguments.). void반환은 무엇이든 받습니다.(...) => void로 타입이 지정된 콜백은 값을 반환해도 됩니다. 호출하는 쪽이 그 값을 쓰지 않겠다고 약속하는 것입니다. 그래서push가 숫자를 반환하는데도list.forEach((x) => other.push(x))가 컴파일됩니다.
매개변수 타입은 여전히 호환되어야 합니다. (a: number, b: number) => number가 기대되는 곳에 (x: string) => ...를 넘기면 Types of parameters 'x' and 'a' are incompatible.로 실패합니다.
호출 시그니처
(n: number) => string은 호출 시그니처를 가진 객체 타입 { (n: number): string }의 줄임말입니다. 함수가 속성도 가질 때는 긴 형태가 필요합니다.
TypeScript에서는 같은 스코프에서 const로 선언한 함수에 속성을 추가할 수 있고, 그 속성을 함수의 타입에 기록합니다. 인터페이스도 호출 시그니처를 담을 수 있습니다: interface Counter { (): number; count: number }.
생성 시그니처는 new로 호출하는 것을 기술합니다. 앞에 new를 붙여 new (name: string) => User, 객체 형태로는 { new (name: string): User }라고 씁니다. 클래스를 받는 매개변수에 타입을 붙이는 방법입니다.
메서드 문법과 속성 문법
객체 타입에서 함수 멤버는 메서드 handle(value: string): void로 쓰거나, 함수를 담는 속성 handle: (value: string) => void로 쓸 수 있습니다. 바꿔 써도 될 것 같지만 strict에서는 다르게 검사됩니다.
컴파일러는 이렇게 보고합니다.
index.ts(8,27): error TS2322: Type '(value: string) => void' is not assignable to type '(value: string | number) => void'.
Types of parameters 'value' and 'value' are incompatible.
Type 'string | number' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.
오류가 옳습니다. onlyStrings는 숫자를 처리할 수 없습니다. 메서드 형태는 같은 실수를 통과시키고(메서드 매개변수는 역사적인 이유로 "이변적으로" 검사됩니다), a.handle(42)는 실행하면 TypeError: value.toUpperCase is not a function으로 멈춥니다. b 줄을 지우면 그 오류를 볼 수 있습니다. 직접 객체 타입을 작성할 때는 속성 형태가 더 많은 실수를 잡습니다.
Function 타입은 피하세요
Function은 모든 함수 값이 만족하는 내장 타입입니다. 원하는 것은 거의 아닙니다. TypeScript가 매개변수도 반환 타입도 모르므로 모든 호출이 허용되고 any를 반환합니다.
Function을 실제 시그니처로 바꾸세요. 정말로 어떤 함수든 받는다면(제네릭 debounce, 로깅 래퍼) (...args: never[]) => unknown이 모든 함수를 받으면서도 결과를 any가 아니라 unknown으로 유지합니다. 입력에 따라 타입이 달라지는 함수는 제네릭을 보세요. 제네릭 함수 타입은 <T>(value: T) => T처럼 생겼습니다.
자주 묻는 질문
TypeScript에서 함수 타입은 어떻게 정의하나요?
화살표 문법을 씁니다: (a: number, b: number) => number. 타입 별칭으로 이름을 붙이고(type Compare = (a: number, b: number) => number;) 그 이름을 변수, 매개변수, 속성에 쓰세요. 매개변수 이름은 문법의 일부지만 문서화 용도일 뿐이어서, 매개변수 이름이 다른 함수도 맞습니다.
TypeScript에서 콜백 매개변수의 타입은 어떻게 지정하나요?
함수 타입을 매개변수의 타입으로 씁니다: function onEach(items: string[], cb: (item: string, index: number) => void) { ... }. 호출하는 쪽은 매개변수가 더 적은 함수를 넘겨도 되고, 콜백의 매개변수는 그 타입에서 추론되므로 onEach(list, (item) => ...)에는 표기가 필요 없습니다.
TypeScript에서 Function 타입을 쓰면 안 되는 이유는 무엇인가요?
Function은 어떤 함수든 받고, 호출해도 검사되지 않습니다. 어떤 인수든 허용되고 결과는 any입니다. 대신 () => void나 (value: string) => number 같은 실제 시그니처를 쓰고, 정말로 모든 함수를 받는다면 (...args: never[]) => unknown을 쓰세요.
함수 타입과 호출 시그니처의 차이는 무엇인가요?
같은 것을 기술합니다. (n: number) => string은 객체 타입 { (n: number): string }의 줄임말입니다. 함수가 속성도 가질 때는 { (n: number): string; label: string }처럼 호출 시그니처 형태가 필요합니다.
TypeScript에서 화살표 함수의 타입은 어떻게 지정하나요?
매개변수와 반환 타입을 인라인으로 표기하거나(const half = (n: number): number => n / 2;), 변수에 함수 타입을 주고 매개변수는 추론에 맡기세요: const half: (n: number) => number = (n) => n / 2;.