목차
이번 주 주제
- 함수 선언 및 실행하기
- 시그니처 오버로딩
- 다형적 함수(제네릭) 맛보기
- 필요성 및 예시
함수 선언 및 호출
Function in JavaScript : 일급 객체.
⇒ 함수를 변수에 할당 가능
⇒ 함수를 다른 함수로 전달 가능
⇒ 함수에서 함수 반환 가능
⇒ 함수의 프로퍼티 선언, 읽기 가능
할수 있는 일이 정말많음
how to type function
function add(a: number, b: number) {
return a + b
}-
보통 매개변수 타입을 명시적으로 정의
: 타입스크립트는 함수 본문에 사용된 타입들은 추론하지만, 매개변수 타입은 특별한 상황을 제외하면 추론하지 않기 때문.
-
반환 타입은 자동으로 추론하기 때문에 명시하지 않아도 됨.
- 반환타입 명시 예
function add(a: number, b: number)**: void** { return a + b }- 반환하는 값이 없을 때 : 반환타입을 void로 정의
여러 함수 선언 방법
함수 표현식, 선언문 자세히보기는 아래 링크 참고
- 이름을 붙인 함수
function add(a: number, b: number) {
return a + b;
}- 함수 표현식
const multiply = function(a: number, b: number) {
return a * b;
};- 화살표 함수 표현식
const subtract = (a: number, b: number) => {
return a - b;
};- 단축형 화살표 함수 표현식(중괄호 없는 버전)
const subtract = (a: number, b: number) => return a - b;const divide = new Function("a", "b", "return a / b") as (a: number, b: number) => number;함수 호출 시그니쳐(Call Signature)
‘타입 시그니쳐(type signature)라고도 한다.
함수의 타입만 정의하고 싶은 경우, 함수 전체 타입을 표현하는 방법.
= 타입 수준의 코드.
getUserInfo 라는 함수가 있다고 해보자.
interface UserInfo {
name: string;
age: number;
}
const getUserInfo = (email: string, password: string) => {
//...
return isUser ? userInfo : null
}해당 함수의 호출 시그니쳐는 다음과 같다.
type GetUserInfo = (email: string, password: string) => null | UserInfo
type GetUesrInfo = {
(email: string, passwrod: stirng): null | UserInfo
}함수는 이처럼 어떤 타입의 인수를 받는지, 어떤 타입을 반환하는지로 설명할 수 있다.
화살표 함수와 비슷한 형태
함수 표현식에서 함수 호출 시그니쳐로 타입할당하기
const **getUserInfo: GetUserInnfo = (
email,
password
)** => {
// ...
return isUser ? null : userInfo;
}⇒ 매개변수의 타입은 호출 시그니쳐에 명시되어있기 때문에, 다시 명시할 필요 없음. : 타입스크립트가 GetUserInfo에서 추론하기 때문(문맥적 타입화).
type Fruit = 'Banana' | 'Apple'-
함수 매개변수의 기본값을 명시하고 싶은데, 호출 시그니쳐에서 명시가 가능할까?
No. 함수 호출 시그니쳐는 타입 수준의 코드이기 때문에 기본값을 명시할 수 없다. 기본값은 타입이 아니라 값이기 때문.
매개변수의 기본값을 명시하려면 함수 선언 부분에서 명시해야한다.
const DEFAULT_PASSWORD = 'elice123!'; const getUserInfo: GetUserInfo = (email, password = DEFAULT_PASSWORD) => { // ... return isUser ? null : userInfo; }
문맥적 타입화(Contextual Typing)
변수, 표현식의 타입을 주변 문맥으로부터 추론하는 타입스크립트의 강력한 타입추론 기능.
코드를 분석할 때 주변에 존재하는 타입 존재를 활용하여 타입을 추론함. 주로 변수 선언, 함수 매개변수, 함수 반환값 등의 위치에서 사용됨.
-
예시
- 변수 할당:
const name = "John"; // 문자열 타입으로 추론됨 const age = 30; // 숫자 타입으로 추론됨 const person = { name, // name 변수의 타입을 추론하여 문자열 타입으로 설정함 age // age 변수의 타입을 추론하여 숫자 타입으로 설정함 };- 함수 매개변수:
function greet(name: string) { console.log(`Hello, ${name}!`); } const person = { name: "John", age: 30 }; greet(person.name); // person.name의 타입을 추론하여 문자열 타입으로 매개변수에 전달함- 함수 반환값
function createGreeting(name: string) { return `Hello, ${name}!`; } const greeting = createGreeting("John"); // createGreeting의 반환값을 추론하여 문자열 타입으로 설정함 배열 초기화:const numbers = [1, 2, 3, 4, 5]; // numbers 배열의 타입을 추론하여 숫자 타입의 배열로 설정함 const doubledNumbers = numbers.map(num => num * 2); // numbers 배열의 요소의 타입을 추론하여 숫자 타입의 배열로 설정함- 객체 초기화:
const person = { name: "John", // name 속성의 타입을 추론하여 문자열 타입으로 설정함 age: 30 // age 속성의 타입을 추론하여 숫자 타입으로 설정함 };개발자가 명시적으로 타입을 작성하지 않아도 코드를 유지보수하기 쉽고 타입 안정성을 확보할 수 있다.
💎 함수 오버로드(Function Overloads)
동일한 함수 이름을 가지지만 매개변수의 개수와 타입, 반환 타입 등이 다른 여러 개의 시그니처를 가지는 것.
다양한 매개변수 조합에 대해 다른 동작을 수행하고 다른 타입을 반환할 수 있다.
- 어떻게 호출 시그니쳐를 여러개 가질 수 있는가?
⇒ 자바스크립트는 동적 타입 언어. 따라서 함수에 넘겨지는 인수 타입에 따라 반환타입이 달라질 수 있다.입력 타입에 따라 달라지는 함수의 출력 타입을 표현할 수 있도록 ‘함수 오버로드’라는 고급 기능으로 지원한다.
구현 시그니처와 오버로드 시그니처
예약하려는 여행 일정에 따라 예약 가능한 항공권 여부를 알려주는 함수를 만든다고 해보자.
type ReserveAirline = {
(from: Date, to: Date, destination: string): boolean
}
const resserveAirline: ReserveAirline = (from, to, destination) => {
// ...
}하지만, 편도 여행 일정에 따른 항공권 여부를 알려주는 경우도 필요하다.
이 때 함수 오버로드를 사용할 수 있다.
type ReserveAirline = {
(from: Date, to: Date, destination: string): boolean
(from: Date, destination: string): boolean
}
const resserveAirline: ReserveAirline = (from, to, destination) => {
// ...
}하지만 ReserveAirline 타입은 오버로드 시그니처가 유니온으로 처리되면서 함수 선언 부분에서 오류가 발생한다. 타입스크립트는 오버로딩을 유니온으로 처리해버린다. 따라서 매개변수의 차이에 따라 함수 선언부분에서 구현 시그니처를 작성해주어야한다.
오류 및 구현 시그니처 작성 확인하기
TS Playground - An online editor for exploring TypeScript and JavaScript (opens in a new tab)
오버로드 시그니처와 구현 시그니처를 헷갈려하는 경우가 많음.
// 오버로드 시그니처
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
// 구현 시그니처
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}구현 시그니처: 위의 두개의 오버로드 시그니처를 합친, 호환 가능한 시그니처. 이 시그니처는 직접 호출되지 않는다. ⇒ 무슨말이냐?
makeDate(5, 12); // typeError
// No overload expects 2 arguments, but overloads do exist that **expect either 1 or 3 arguments.**타입스크립트는 함수 오버로드 시그니처에 따라 매개변수가 1개 혹은 3개를 받으려고 한다.
구현 시그니처 부분에서 d, y 매개변수를 옵셔널로 설정했기 때문에 두개의 매개변수만 넘겨줘도 될 것 같지만, 오버로드 시그니처부분에서는 1개 혹은 3개 전부를 받고 있으므로 2개로 makeDate 를 호출할 수 없다.
다시 한번 강조하지만, 함수 본문을 작성하기 위해 사용된 시그니처는 외부에서 “보이지 않습니다”.
(타입스크립트 공식문서 핸드북)
또한 구현 시그니처는 오버로드된 시그니처와 호환되어야한다.
구현 시그니처가 오버로드들과 올바르게 일치하지 않아서 오류가 발생하는 예시 (opens in a new tab)
좋은 오버로드 작성하기
문자열 혹은 배열의 길이를 반환하는 함수
function len(s: string): number;
function len(arr: any[]): number;
// OK
function len(x: any) {
return x.length;
}TypeScript는 하나의 오버로드를 통해서만 함수를 해석한다.
우리는 이 함수를 문자열 또는 배열이 될 수 있는 값을 통해서 호출할 수 없다.
len(""); // string OK
len([0]); // array OK
len(Math.random() > 0.5 ? "hello" : [0]); // 인수의 타입이 string | array
// Error
No overload matches this call.
Overload 1 of 2, '(s: string): number', gave the following error.
Argument of type 'number[] | "hello"' is not assignable to parameter of type 'string'.
Type 'number[]' is not assignable to type 'string'.
Overload 2 of 2, '(arr: any[]): number', gave the following error.
Argument of type 'number[] | "hello"' is not assignable to parameter of type 'any[]'.
Type 'string' is not assignable to type 'any[]'.No overload matches this call.
Overload 1 of 2, '(s: string): number', gave the following error.
Argument of type 'number[] | "hello"' is not assignable to parameter of type 'string'.
Type 'number[]' is not assignable to type 'string'.
Overload 2 of 2, '(arr: any[]): number', gave the following error.
Argument of type 'number[] | "hello"' is not assignable to parameter of type 'any[]'.
Type 'string' is not assignable to type 'any[]'.Try
함수 오버로딩을 하면 매개변수 타입에 따라 그에 맞는 오버로드 시그니쳐 하나로 타입을 해석하는데, 이 경우는 함수 실행마다 매개변수의 타입이 달라짐.
이 경우, 함수 오버로딩을 하는 것보다 매개변수에 유니온타입을 사용하는 것이 더 권장된다.
가능하다면 오버로드 대신 유니온 타입을 사용하십시오
: 두 오버로드 시그니처 모두 매개변수 개수가 같으며, 같은 반환타입을 가지기 때문에.
function len(x: any[] | string) {
return x.length;
}함수 오버로딩이 사용되는 예시
함수 오버로드는 여러 라이브러리 d.ts 코드에서 자주 발견된다.
이해하지 못하면 소스 코드도 못 뜯어본다!
유용하게 활용되는 예시로 좀더 함수 오버로드를 이해해보자!
1. document.createElement() (opens in a new tab)
대표적으로, DOM API에서 유용하게 활용된다.
예를 들어 DOM 요소를 만드는 createElement()함수는
- HTML 태그 문자열(
'a','div','button'…) 을 받아서 - 해당 태그 타입의 새 HTML 요소(
HTMLAnchorElement,HTMLDivElement,HTMLButtonElement) 반환함. (HTML 요소 타입은 타입스크립트 내장타입으로 지원)
인수로 받은 태그 문자열에 따라 반환타입이 달라지므로 유용하게 사용될 수 있음.
// 함수 전체 호출 시그니쳐
type CreateElement = {
(tagName: 'a'): HTMLAnchorElement
(tagName: 'div'): HTMLDivElement
(tagName: 'button'): HTMLButtonElement
(tagName: 'table'): HTMLTableElement
// ...(생략)
**(tagName: string): HTMLElement**
}마지막부분 : tagName에 커스텀 태그명 혹은 타입스크립트 내장 타입으로 지원하지 않는 최신 태그명이 전달되는 경우 고려하여 일반적인 HTMLElement (opens in a new tab) 타입 반환.
타입스크립트는 선언한 순서대로 오버로드를 해석한다.
⇒ 오버로드에 명시되지 않은 문자열을 인수로 전달한 경우 HTMLElement 타입을 반환하도록 분류됨.
오버로드 한 함수 구현
const createElement: CreateElement = (tagName: string) => {
//...
}함수 오버로드에서 명시한 ‘모든 매개변수의 타입을 합친 타입으로 명시한다.
여기서는 ‘a’ | ‘div’ | ‘button’ | ‘table’ | … | string 가 되고, 문자열 리터럴 타입은 string의 서브타입이므로 string으로 축약할 수 있다.
함수 선언을 오버로드 하기
위에서는 함수 호출 시그니처(타입 수준)을 오버로드했지만, 함수 선언을 오버로드 할 수도 있다.
function createElement(tagName: 'a'): HTMLAnchorElement
function createElement(tagName: 'div'): HTMLDivElement
...2. 날짜 형식 관련 라이브러리
우리가 사용하는 dayjs나 moment, phonenumber.js 같은 date나 전화번호 등 다양한 형식을 처리하는 라이브러리에서 함수 오버로드를 쉽게 찾아볼 수 있다.
매개변수에 따라 다양한 형식을 반환하는 이런 포맷팅함수를 사용해봤다면 그 유용함을 알 것..!
예시: dayjs()
dayjs/index.d.ts at b3624de619d6e734cd0ffdbbd3502185041c1b60 · iamkun/dayjs (opens in a new tab)
declare function dayjs (date?: dayjs.ConfigType): dayjs.Dayjs
declare function dayjs (date?: dayjs.ConfigType, format?: dayjs.OptionType, strict?: boolean): dayjs.Dayjs
declare function dayjs (date?: dayjs.ConfigType, format?: dayjs.OptionType, locale?: string, strict?: boolean): dayjs.Dayjs함수 dayjs 에 넘겨지는 매개변수는 상황에 따라 다양하다.
여기서는 매개변수의 개수에 따라 반환타입이 달라지지는 않지만, 함수의 동작이 달라진다.
- 매개변수를 넘기지 않는 경우
Calling
dayjs()without parameters returns a fresh Day.js object with the current date and time.
: 현재 일시 정보를 반환
const now = dayjs();매개변수로 받은 날짜 정보를 담은 Dayjs 형식의 객체를 반환한다.
(이때 매개변수의 타입인 ConfigType 은 string | number | Date | Dayjs | null | undefined 에 해당함.)
dayjs('2018-04-04T16:00:00.000Z') // ISO8601 형식의 string
dayjs(1318781876406) // unix Timestamp 형식의 number
const date = new Date(2023, 6, 6);
const memorialDay = dayjs(date) // Date 타입
// Dayjs 객체 타입
dayjs({ year :2010, month :3, day :5, hour :15, minute :10, second :3})이 외에도 date, format을 넘기는 경우, date, format , locale을 넘기는 경우 등 다양함.
💎 제네릭 함수(Generic Functions)(맛보기)
지금까지 살펴본 함수 타입은 구체 타입(concrete type)을 사용하는 경우였다.
= 기대하는 타입을 정확히 알고 있고, 이 타입이 맞게 전달되었는지 확인하는 경우 구체타입을 사용한다.
하지만 프로그램을 설계할 때 어떤 타입이 사용될지 알 수 없는 상황도 존재하기 마련이다.
⇒ 특정 타입으로 제한할 수 없고, 상황에 따라 알아서 타입스크립트가 어떤 타입이 사용되었는지에 따라 추론을 하도록 ‘제네릭 타입’을 사용한다.
예시로 필요성을 알아보자
주어진 배열의 첫 번째 요소를 반환하는 함수가 있다고 해보자.
function getFirstElement = (arr: any[]) => {
return arr[0];
}배열이 어떤요소도 가질 수 있기 때문에 매개변수 타입을 any[]로 명시했지만, 아쉽게도 이 경우 해당 함수의 반환값의 타입 또한 any로 추론된다.
TS Playground - An online editor for exploring TypeScript and JavaScript (opens in a new tab)
함수 호출시 넘겨준 인수의 타입에 따라 반환값의 타입도 추론되게 하기위해선 어떻게해야할까?
함수 오버로드를 사용해볼까?
type GetElement = {
(arr: number[]): number;
(arr: string[]): string;
(arr: boolean[]): boolean;
// ...
(arr: object[]): object;
}원시 타입의 경우는 문제 없지만, 이런… object 타입은 객체의 실제 형태( property의 구조)에 대한정보는 아무것도 제공하지 못한다.
따라서 다음과 같이 반환값으로부터 프로퍼티에 접근하려 하는 경우 에러가 발생한다.
const user = getElement({name: 'jisu', level: 1234});
const userName = user.name // typeError: 'name' property does not exsit in 'object' type.매개변수로 받을 객체의 형태를 알지 못한다면(프로퍼티가 특정되지 않은경우) 제네릭을 사용하자.
// 전체 호출 시그니처T
type GetElement = {
<T>(arr: T[]): T
}
// 단축 호출 시그니처
type GetElemnt = <T>(arr: T[]) => T
const getElement: GetElement = (arr) = {
return arr[0];
}
// 혹은 함수 선언과 함께 시그니처를 작성할 수 있다.
// 함수 선언문과 사용
function getElement<T>(arr: T[]): T {
return arr[0]
}
type GetElement<T> = {
name: T;
nickname: T;
}
const myElement: GetElement = {name: 'hi', nickname: 'hihi'}이렇게 함수 호출 시점에서 T의 타입이 추론이 되도록 사용가능하다.
여기서 T 는 제네릭 타입 매개변수(generic type parameter)라고 한다.
꺽쇠 괄호로 제네릭 타입의 매개변수임을 선언하고, 이 꺽쇠 기호의 위치에 따라 제네릭 범위가 결정된다.
일반적으로 T, U, V... 를 사용하지만 어떤 이름도 올 수 있다.
type GetElement = {
<ArrElement>(arr: ArrElement[]): ArrElement
}