자바스크립트에서는 함수 '문장'과 함수 '표현식'을 다르게 인식한다.
function rollDice1(sides: number): number {
/*...*/
}
const rollDice2 = function (sides: number): number {
/*...*/
};
const rollDice3 = (sides: number): number => {
/* ... */
};
▶ 타입스크립트에서는 함수 표현식을 사용하는 것이 좋다!
함수의 매개변수부터 반환값까지 전체를 함수타입으로 선언하여 함수 표현식에 재사용할 수 있기 때문
type DiceRollFn = (sides: number) => number;
const rollDice: DiceRollFn = sides => {/*...*/};
type BinaryFn = (a: number, b: number) => number;
const add: BinaryFn = (a, b) => a+b;
const sub: BinaryFn = (a, b) => a-b;
const mul: BinaryFn = (a, b) => a*b;
const div: BinaryFn = (a, b) => a/b;
함수 시그니처란?
- 함수 타입 문법을 의미한다.
(a: number, b: number) => number; (a: number, b: number): number;
라이브러리는 공통 함수 시그니처를 타입으로 제공하기도 한다.
ex) 리액트는 매개변수에 명시하는 MouseEvent
타입 대신에 함수 전체에 적용할 수 있는 MouseEventHandler
타입을 제공한다.
시그니처가 일치하는 다른 함수가 있을 때, 함수 표현식에 타입 적용하기
ex) 웹브라우저 fetch
에 관련된 예시를 보자
fetch
: 특정 리소스에 HTTP 요청을 보내고, response.json() 또는 response.text()를 사용해 응답의 데이터를 추출
async function getQuote() {
const response = await fetch(`/quote?by=Mark+Twain`);
const quote = await response.json();
return quote;
}
❓ 여기에 버그가 있다?
/quote
가 존재하지 않는 API라면, '404 Not Found'가 포함된 내용을 응답함. 그 때 응답이 JSON 형식이 아니라면 오류 메시지를 담아 거절된 프로미스를 반환할 것. 이 오류 메시지에 의해 실제 오류인 404가 감추어진다.checkedFetch
함수를 작성해 본다면// 매개변수에 직접 타입 선언
async function checkedFetch(input: RequestInfo, init?: RequestInit) {
const response = await fetch(input, init);
if (!response.ok) {
//비동기 함수 내에서 거절된 프로미스로 변환
throw new Error("Request failed: " + response.status);
}
return response;
}
🔽이 코드를 지향하자🔽
// typeof fetch를 사용하여 함수 전체에 타입 선언
const checkedFetch: typeof fetch = async (input, init) => {
const response = await fetch(input, init);
if (!response.ok) {
throw new Error("Request failed: " + response.status);
}
return response;
};
fetch의 타입 선언 (lib.dom.d.ts)
declare function fetch( input: RequestInfo, init?: RequestInit ): Promise<Response>;
▶ 왜 이 코드를 지양해야 하는가?
타입이 선언되어 있는 fetch의 타입을 이용함으로써
RequestInfo
), init(RequestInit
)의 타입을 추론할 수 있게 해줄 뿐더러,Proise<Response>
)까지 보장한다.✅ 결론 : 다른 함수의 시그니처와 동일한 타입을 가지는 새 함수를 작성하거나, 동일한 타입 시그니처를 가지는 여러 개의 함수를 작성할 때 매개변수의 타입과 반환타입을 반복해서 작성하지 말고 함수 전체의 타입 선언을 적용하자
타입스크립트에서 명명된 타입을 정의하는 방법 두가지
type TState = {
name: string;
capital: string;
}
interface IState {
name: string;
capital: string;
}
( 인터페이스 대신 클래스를 사용할 수도 있지만, 클래스는 값으로도 쓰일 수 있는 자바스크립트 런타임의 개념이다.)
타입 네이밍에 대여
❗ 교재에서는 인터페이스를 I, 타입을 T로 시작해 구분이 쉽게 표현하였으나, 실제 코드에서는 이렇게 해서는 안된다!
인터페이스 접두사를 붙이는 것은 C#에서 비롯된 관례로, 현재는 지양해야 할 스타일로 여겨진다.
const wyoming: TState = { // IState로 해도 마찬가지
name: 'Wyoming',
capital: 'Cheyenne',
population: 500_500 // error: 'TState' 형식에 'population'이가 없습니다.
}
type TDict = { [key: string]: string };
interface IDict {
[key: string]: string;
}
type TFn = (x: number) => string;
interface IFn {
(x: number): string;
}
const toStrT: TFn = x => '' + x;
const toStrI: IFn = x => '' + x;
type TPair<T> = {
first: T;
second: T;
}
interface IPair<T> {
first : T;
second: T;
}
interface IStateWithPop extends TState {
population: number;
}
type TStateWithPop = IState & { population: number; };
❗ 그런데 인터페이스는 유니온 타입 같은 복잡한 타입을 확장하지는 못한다!
복잡한 타입을 확장하고 싶다면 타입과 &를 사용해야 한다.
class StateT implements TState {
name: string = '';
capital: string = '';
}
class StateI implements IState {
name: string = '';
capital: string = '';
}
type AorB = 'a' | 'b';
❓ 그럼 인터페이스가 유니온 타입 확장이 필요하다면?
type Input = { /* */ };
type Output = { /* */ };
interface VaiableMap {
[name: string] : Input | Output;
}
▶ 별도의 두 타입을 하나의 변수명으로 매핑하여 만들기
type NamedVariable = (Input | Output) & {name: string};
또, 튜플과 배열 타입도 type 키워드를 이용해 더 간결하게 표현할 수 있다.
type Pair = [number, number];
type StringList = string[];
type NamedNums = [string, ...number[]];
🔽 인터페이스로 비슷하게 구현할 수는 있다.
interface Tuple {
0: number;
1: number;
length: 2;
}
const t: Tuple = [10, 20]
❗ 그러나 concat 같은 메서드들을 사용할 수는 없다.
✅ 따라서 튜플은 type 키워드로 구현하는 것이 낫다.
interface IState {
name: string;
capital: string;
}
interface IState {
population: number;
}
const wyoming: IState = {
name: 'Wyoming',
capital: 'Cheyenne',
population: 500_000 //wjdtkd
};
✅ 결론
타입 별칭이란?
특정 타입이나 인터페이스를 참조할 수 있는 타입 변수를 의미// string 타입을 사용할 때 const name: string = 'capt'; // 타입 별칭을 사용할 때 type MyName = string; const name: MyName = 'capt';
함수 시그니처 부분 대박,, 생각지 못했던 부분인데 저런 버그가 생길 수도 있었네요..!! 배워갑니당 ㅎㅎ
그리고 type이랑 interface의 차이점을 항상 궁금해하기만 하고 찾아보지는 않았었는데 자세하게 정리해주셔서 확실하게 알게 됐어요 :) 감사합니다!