[LearningTS] Chapter05. 함수
포스트
취소

[LearningTS] Chapter05. 함수

CHAPTER 05. 함수

1
한쪽 끝에는 함수 인수가 있고 다른 쪽 끝에는 반환 타입이 있습니다.

🌓 함수 매개변수

[예제]
sing 함수는 song 매개변수를 받아 콘솔에 출력한다.

1
2
3
function sing(song) {
  console.log(`Singing: ${song}!`);
}
  • 명시적 타입 정보가 선언되지 않으면 절대 타입을 알 수 없다.
  • 타입스크립트가 이를 any 타입으로 간주하며 매개변수의 타입은 무엇이든 될 수 있다.
  • 변수와 마찬가지로 타입스크립트를 사용하면 타입 애너테이션으로 함수 매개변수의 타입을 선언할 수 있다.
1
2
3
function sing(song: string) {
  console.log(`Singing: ${song}!`);
}
  • 코드를 유효한 타입스크립트 구문으로 만들기 위해 함수 매개변수에 적절한 타입 애너테이션을 추가할 필요는 없다.
  • 타입스크립트는 타입 오류로 오류를 계속 알리지만, 이미 시작된 자바스크립트는 게속 실행된다.


1. 필수 매개변수

  • 자바스크립트에서는 인수의 수와 상관없이 함수를 호출할 수 있다.
  • 하지만 타입스크립트는 함수에 선언된 모든 매개변수가 필수라고 가정한다.
  • 함수가 잘못된 수의 인수로 호출되면, 타입스크립튼느 타입 오류의 형태로 이의를 제기한다.
  • 함수가 너무 적거나 많은 인수로 호출되면 타입스크립트는 인수의 개수를 계산한다.

[예제]

1
2
3
4
5
function singTwo(first: string, second: string) {
  console.log(`${first} / ${second}`);
}

singTwo("I will Survie", "Higher Love"); // Ok

  • 함수에 필수 매개변수를 제공하도록 강제하면 예상되는 모든 인숫값을 함수 내에 존재하도록 만들어 타입 안정성을 강화하는 데 도움이 된다.
  • 모든 인숫값이 존재하는지 확인하지 못하면 이전 singTwo 함수가 undefined 로그로 남기거나 인수를 무시하는 것과 같이 코드에서 예기치 않은 동작이 발생한다.


2. 선택적 매개변수

  • 자바스크립트에서 함수 매개변수가 제공되지 않으면 함수 내부의 인숫값을 undefined으로 기본 설정이 되지만, 타입스크립트는 인수를 제공하지 못하는 경우, 타입 오류가 발생한다.
  • 타입스크립트에서 선택적 객페 타입 속성과 유사하게 타입 애너테이션의 : 앞에 ?를 추가해 매개변수가 선택적이라고 표시한다.
  • 함수 호출에 선택적 매개변수를 제공할 필요는 없다.
  • 선택적 매개변수에는 항상 | undefined가 유니언 타입으로 추가되어 있다.

[예제 1]

  • announceSong 함수에서 singer 매개변수는 선택 사항으로 표시된다.
  • 타입은 stringundefined이며 함수 호출자가 singer 매개변수를 위한 인수를 제공할 필요가 없다.
  • 만일 singer가 제공되면, string 값이거나 undefined일 수 있다.
1
2
3
4
5
6
7
8
9
10
11
function announceSong(song: string, singer?: string) {
  console.log(`Song: ${song}`);

  if (singer) {
    console.log(`Singer: ${singer}`);
  }
}

announceSong('Greensleeves'); // Ok
announceSong('Greensleeves', undefined); // Ok
announceSong('Greensleeves', 'Sia'); // Ok
  • 이러한 선택적 매개변수는 항상 암묵적으로 undefined가 될 수 있다.
  • 이전 코드에서 singer눈 stringundefined 타입으로 시작한 후에 if 문에 따라 string 타입으로 좁혀진다.
  • 선택적 매개변수는 | undefined를 포함하는 유니언 타입 매개변수와는 다르며, ?으로 표시된 선택적 매개변수가 아닌 매개변수는 값이 명시적으로 undefined일지라도 항상 제공되어야 한다.

[예제 2]

  • announceSongBy 함수의 singer 매개변수는 명시적으로 제공되어야 한다.
  • singer는 string 값이거나 undefined가 될 수 있다.
1
2
3
4
5
6
function announceSongBy(song: string, singer: string | undefined) {
  /*...*/
}

announceSongBy('Greensleeves', undefined); // Ok
announceSongBy('Chandelier', 'Sia'); // Ok

  • 함수에서 사용되는 모든 선택적 매개변수는 마지막 매개변수여야 한다.
  • 필수 매개변수 전에 선택적 매개변수를 위치시키면 다음과 같이 타입스크립트 구문 오류가 발생한다.


3. 기본 매개변수

  • 자바스크립트에서 선택적 매개변수를 선언할 때 =와 같이 값이 포함된 기본값을 제공할 수 있다.
  • 즉, 선택적 매개변수에는 기본적으로 값이 제공되기 때문에 해당 타입스크립트 타입에는 암묵적으로 함수 내부에 | undefined 유니언 타입이 추가된다.
  • 타입스크립트는 함수의 매개변수에 대해 인수를 누락하거나 undefined 인수를 사용해서 호출하는 것을 여전히 허용한다.
  • 타입스크립트의 타입 추론은 초기 변숫값과 마찬가지로 기본 함수 매개변수에 대해서도 유사하게 작동한다.
  • 매개변수에 기본값이 있고 타입 애너테이션이 없는 경우, 타입스크립트는 해당 기본값을 기반으로 매개변수 타입을 유추한다.

[예제]
rateSong 함수에서 rating은 number 타입으로 유추되지만, 함수를 호출하는 코드에서는 선택적 number | undefined가 된다.

1
2
3
4
5
6
7
function rateSong(song: string, rating = 0) {
  console.log(`${song} gets ${rating}/5 stars!`);
}

rateSong('Photograph');
rateSong('Set Fire to Rain', 5);
rateSong('Set Fire to the Rain', undefined);


4. 나머지 매개변수

  • 자바스크립트의 일부 함수는 임의의 수의 인수로 호출할 수 있도록 만들어진다.
  • ... 스프레드 연산자는 함수 선언의 마지막 매개변수에 위치하고, 해당 매개변수에서 시작해 함수에 전달된 ‘나머지(rest)’ 인수가 모두 단일 배열에 저장되어야 함을 나타낸다.
  • 타입스크립트는 이러한 나머지 매개변수의 타입을 일반 매개변수와 유사하게 선언할 수 있다.
  • 단 인수 배열을 나타내기 위해 끝에 [] 구문이 추가된다는 점만 다르다.

[예제]
singAllTheSongs는 songs 나머지 매개변수에 대해 0개 이상의 string 타입 인수를 사용할 수 있다.

1
2
3
4
5
6
7
8
function singAllTheSongs(singer: string, ...songs: string[]) {
  for (const song of songs) {
    console.log(`${song}, by ${singer}`);
  }
}

singAllTheSongs('Alicia Keys'); // Ok
singAllTheSongs('lady Gaga', 'Bad Romance', 'Just Dance', 'Poker Face'); // Ok


🌓 반환 타입

  • 타입스크립트는 지각적이다.
  • 함수가 반환할 수 있는 가능한 모든 값을 이해하면 함수가 반환하는 타입을 알 수 있다.

[예제 1]

1
2
3
4
5
6
7
8
// 타입: (songs: string[]) => number
function singSongs(songs: string[]) {
  for (const song of songs) {
    console.log(`${song}`);
  }

  return songs.length;
}

함수에 다른 값을 가진 여러 개의 반환값을 포함하고 있다면 타입스크립트는 반환 타입을 가능한 모든 반환 타입의 조합으로 유추한다.

[예제 2]

  • getSongAt 함수는 stringundefined를 반환하는 것으로 유추된다.
  • 두 가지 가능한 반환값이 각각 string과 undefined이기 때문이다.
1
2
3
4
5
6
// 타입: (songs: string[], index: number) => string | undefined
function getSongAt(songs: string[], index: number) {
  return index < songs.length 
    ? songs[index] 
    : undefined;
}


명시적 반환 타입

변수와 마찬가지로 타입 애너테이션을 사용해 함수의 반환 타입을 명시적으로 선언하지 않는 좋지만, 함수에서 반환 타입을 명시적으로 선언하는 방식이 매우 유용할 때가 종종 있다.

  • 가능한 반환값이 많은 함수가 항상 동일한 타입의 값을 반환하도록 강제한다.
  • 타입스크립트는 재귀 함수의 반환 타입을 통해 유추하는 것을 거부한다.
  • 수백 개 이상의 타입스크립트 파일이 있는 매우 큰 프로젝트에서 타입스크립트 타입 검사 속도를 높일 수 있다.

함수 선언 반환 타입 에너테이션은 매개변수 목록이 끝나는 ) 다음에 배치되며, 함수 선언의 경우는 { 앞에 배치된다.

1
2
3
function singSongRecursive(songs: string[], count = 0): number {
  return songs.length ? singSongRecursive(songs.slice(1), count + 1) : count;
}

화살표 함수의 경우 => 앞에 배치된다.

1
2
3
const singSongRecursive = (songs: string[], count = 0): number => {
  return songs.length ? singSongRecursive(songs.slice(1), count + 1) : count;
};

함수의 반환문이 함수의 반환 타입으로 할당할 수 없는 값을 반환하는 경우 타입스크립트는 할당 가능서 오류를 표시한다.

[예제]
getSongRecordingDate 함수는 Date | undefiend를 반환하도록 명시적으로 선언되었지만, 반환문 중 하나가 string을 반환하도록 잘못 제공하고 있다.


🌓 함수 타입

  • 자바스크립트에서는 함수의 값으로 전달할 수 있다.
  • 즉, 함수를 가지기 위한 매개변수 또는 변수의 타입을 선언하는 방법이 필요하다.
  • 함수 타입 구문은 화살표 함수와 유사하지만 함수 본문 대신 타입이 있다.

[예제 1]
nothingInGiveString 변수 타입은 매개변수가 없고 string 타입을 반환하는 함수다.

1
let nothingInGiveString: () => string;

[예제 2]
inputAndOutput 변수 타입은 string[] 매개변수와 count 선택적 매개변수 및 number 값을 반환하는 함수임을 설명한다.

1
let inputAndOutput: (songs: string[], count?: number) => number;

함수 타입은 콜백 매개변수(함수로 호출되는 매개변수)를 설명하는 데 자주 사용된다.

[예제 3]

  • runOnSongs 함수는 getSongAt 매개변수의 타입을 index: number를 받고 string을 반환하는 함수로 선언했다.
  • getSongAt을 전달하면 해당 타입과 일치하지만, logSong은 매개변수로 number 대신 string을 사용하므로 반환값을 가져오는 데 실패한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const songs = ['Juice', 'Shake It Off', "What's Up"];

function runOnSongs(getSongAt: (index: number) => string) {
  for (let i = 0; i < songs.length; i++) {
    console.log(getSongAt(i));
  }
}

function getSongAt(index: number) {
  return `${songs[index]}`;
}
runOnSongs(getSongAt); // Ok

function logSong(song: string) {
  return `${song}`;
}

runOnSongs(logSong)에 대한 오류 메시지는 할당 가능성 오류의 예로 몇 가지 상세한 단계까지 제공한다.

두 함수를 서로 할당할 수 없다는 오류를 출력할 때 타입스크립트는 일반적으로 세 가지 상세한 단계를 제공한다.

  1. 첫 번째 들여쓰기 단계는 두 함수 타입을 출력한다.
  2. 다음 들여쓰기 단계는 일치하지 않는 부분을 지정한다.
  3. 마지막 들여쓰기 단계는 일치하지 않는 부분에 대한 정확한 할당 가능성 오류를 출력한다.

이전 코드 스니핏에서 단계별로 제공하는 내용은 다음과 같다.

  1. logSong: (song: string) => string은 getSongAt: (index: number) => string에 할당되도록 제공된 타입이다.
  2. logSong의 song 매개변수는 getSongAt의 index의 매개변수로 할당된다.
  3. song의 string 타입은 index의 number 타입에 할당할 수 없다.


1. 함수 타입 괄호

  • 함수 타입은 다른 타입이 사용되는 모든 곳에 배치할 수 있다.
  • 여기에는 유니언 타입도 포함된다.
  • 유니언 타입의 애너테이션에서 함수 반환 위치를 나타내거나 유니언 타입을 감싸는 부분을 표시할 때 괄호를 사용한다.
1
2
3
4
5
// 타입은 string | undefined 유니언을 반환하는 함수
let returnStringOrUndefined: () => string | undefined;

// 타입은 undefined나 string을 반환하는 함수
let maybeReturnString: (() => string) | undefined;

2. 매개변수 타입 추론

  • 매개변수로 사용되는 인라인 함수를 포함하여 작성한 모든 함수에 대해 매개변수를 선언해야 한다면 번거로울 것이다.
  • 다행히도 타입스크립트는 선언된 타입의 위치에 제공된 함수의 매개변수 타입을 유추할 수 있다.

[예제 1]
singer 변수는 string 타입의 매개변수를 갖는 함수로 알려져 있으므로 나중에 singer가 할당되는 함수 내의 song 매개변수는 string으로 예측된다.

1
2
3
4
5
6
let singer: (song: string) => string;

singer = function(song) {
  // song: string의 타입
  return `Singer: ${song.toUpperCase()}!`; // Ok
}

함수를 매개변수로 갖는 함수에 인수로 전달된 함수는 해당 매개변수 타입도 잘 유추할 수 있다.

[예제 2]
song과 index 매개변수 타입스크립트에 따라 각각 string과 number로 유추된다.

1
2
3
4
5
6
7
const songs = ['Call Me', 'Jolene', 'The Chain'];

// song: string
// index: number
song.forEach((song, index) => {
  console.log(`${song} is at index ${index}`);
});


3. 함수 타입 별칭

  • 함수 타입에서도 동일하게 타입 별칭을 사용할 수 있다.

[예제 1]

  • StringToNumber 타입은 strng 타입을 받고 Number 타입을 반환하는 함수의 별칭을 지정한다.
  • 별칭은 이후 변수 타입을 설명하는 데 사용한다.
1
2
3
4
5
type StringToNumber = (input: string) => number;

let stringToNumber: StringToNumber;

stringToNumber = (input) => input.length;

[예제 2]

  • 비슷하게 함수 매개변수도 함수 타입을 참조하는 별칭을 입력할 수 있다.
  • useNumberToString 함수는 함수 타입 별칭인 NumberToString의 단일 매개변수를 갖는다.
1
2
3
4
5
6
7
type NumberToString = (input: number) => string;

function useNumberToString(numberToString: NumberToString) {
  console.log(`The string is: ${numberToString(1234)}`);
}

useNumberToString((input) => `${input}! Hooray!`); // Ok

타입 별칭은 특히 함수 타입에 유용하며, 타입 별칭을 이용하면 반복적으로 작성하는 매개변수와 반환 타입을 갖는 코드 공간을 많이 절약할 수 있다.


🌓 그 외 반환 타입

1. void 반환 타입

  • 일부 함수는 어떤 값도 반환하지 않는다.
  • 예를 들면 return 문이 없는 함수이거나 값을 반환하지 않는 return 문을 가진 함수일 경우다.
  • 타입스크립트는 void 키워드를 사용해 반환 값이 없는 함수의 반환 타입을 확인할 수 있다.

[예제 1]
logSong 함수는 void를 반환하도록 선언되었으므로 값 반환을 허용하지 않는다.

  • 함수 타입 선언 시 void 반환 타입은 매우 유용하다.
  • 함수 타입을 선언할 때 void를 사용하면 함수에서 반환되는 모든 값은 무시된다.

[예제 2]
songLogger 변수는 song: string을 받고, 그 값을 반환하지 않는 함수다.

1
2
3
4
5
6
7
let songLogger: (song: string) => void;

songLogger = (song) => {
  console.log(song);
};

songLogger('Heart of Glass'); // Ok
  • 자바스크립트 함수는 실젯값이 반환되지 않으면 기본이 모두 undefined를 반환하지만 void는 undefined와 동일하지 않는다.
  • void는 함수의 반환 타입이 무시된다는 것을 의미하고, undefined는 반환되는 리터럴 값이다.

[예제 3]
undefined를 포함하는 대신 void 타입의 값을 할당하려고 하면 오류가 발생한다.

1
2
3
4
5
function returnsVoid() {
  return;
}

let lazyValue: string | undefined;

  • undefiend와 void를 구분해서 사용하면 매우 유용하다.
  • 특히 void를 반환하도록 선언된 타입 위치에 전달된 함수가 반한된 모든 값을 무시하도록 설정할 때 유용하다.
  • 배열의 내장 forEach 메서드는 void를 반환하는 콜백을 받는다.
  • forEach에 제공되는 함수는 원하는 모든 값을 반환할 수 있다.

[예제 4]
saveRecords 함수의 records.push(record)는 number(배열의 .push에서 반환된 값)를 반환하지만, 여전히 newRecords.forEach에 전달된 화살표 함수에 대한 반환값이 허용된다.

1
2
3
4
5
6
7
const records: string[] = [];

function saveRecords(newRecords: string[]) {
  newRecords.forEach((record) => records.push(record));
}

saveRecords(['21', 'Come On Over', 'The Bodyguard']);
  • void 타입은 자바스크립트가 아닌 함수의 반환 타입을 선언하는 데 사용하는 타입스크립트 키워드다.
  • void 타입은 함수의 반환값이 자체적으로 반환될 수 있는 값도 아니고, 사용하기 위한 것도 아니라는 표시를 기억해야 한다.


2. never 반환 타입

  • 일부 함수는 값을 반환하지 않을 뿐만 아니라 반환할 생각도 전혀 없다.
  • never 반환 함수는 (의도적으로) 항상 오류를 발생시키거나 무한 루프를 실행하는 함수다.
  • 함수가 절대 반환하지 않도록 의도하려면 명시적 : never 타입 애너테이션을 추가해 해당 함수를 호출한 후 모든 코드가 실행되지 않음을 나타낸다.

[예제]
fail 함수는 오류만 발생시키므로 param의 타입을 string으로 좁혀서 타입스크립트의 제어 흐름 분석을 도와준다.

1
2
3
4
5
6
7
8
9
10
11
12
function fail(message: string): never {
  throw new Error(`Invariat failure: ${message}.`);
}

function workWithUnsageParam(param: unknown) {
  if (typeof param !== 'string') {
    fail(`param should be a string, not ${typeof param}`);
  }

  // 여기에서 param의 타입은 string으로 알려진다.
  param.toUpperCase();
}

🌿 nevervoid와 다르다. void는 아무것도 반환하지 않는 함수를 위한 것이고, never는 절대 반환하지 않는 함수를 위한 것이다.


🌓 함수 오버로드

  • 일부 자바스크립트 함수는 선택적 매개변수와 나머지 매개변수만으로 표현할 수 없는 매우 다른 매개변수들로 호출될 수 있다.
  • 이러한 함수는 오버로드 시그니처라고 불리는 타입스크립트 구문으로 설명할 수 있다.
  • 즉, 하나의 최종 구현 시그니처와 그 함수의 본문 앞에 서로 다른 버전의 함수 이름, 매개변수, 반환 타입을 여러 번 선언한다.
  • 오버로드된 함수 호출에 대해 구문 오류를 생성할지 여부를 결정할 때 타입스크립트는 함수의 오버로드 시그니처만 확인한다.
  • 구현 시그니처는 함수의 내부 로직에서만 사용된다.

[예제]

  • createDate 함수는 1개의 timestamp 매개변수 또는 3개의 매개변수(month, day, year)를 사용해 호출한다.
  • 허용된 수의 인수를 사용해 호출할 수 있지만 2개의 인수를 사용해 호출하면 2개의 인수를 허용하는 오버로드 시그니처가 없기 때문에 타입 오류가 발생한다.
  • 예제의 처음 두 줄은 오버로드 시그니처이고 세 번째 줄은 구현 시그니처 코드다.
1
2
3
4
5
6
7
8
9
10
11
12
13
// ============== 오버로드 시그니처 ==============
function createDate(timestamp: number): Date;
function createDate(month: number, day: number, year: number): Date;

// ============== 구현 시그니처 ==============
function createDate(monthOrTimeStamp: number, day?: number, year?: number) {
  return day === undefined || year === undefined
    ? new Date(monthOrTimeStamp)
    : new Date(year, monthOrTimeStamp, day);
}

createDate(554356800); // Ok
createDate(7, 27, 1987); // Ok

  • 타입스크립트를 컴파일해 자바스크립트로 출력하면 다른 타입 시스템 구문처럼 오버로드 시그니처도 지워진다.

이전 코드 스니펫의 함수는 다음 자바스크립트처럼 컴파일 된다.

1
2
3
4
5
function createDate(monthOrTimeStamp, day, year) {
  return day === undefined || year === undefined
    ? new Date(monthOrTimeStamp)
    : new Date(year, monthOrTimeStamp, day);
}

🌿 함수 오버로드는 복잡하고 설명하기 어려운 함수 타입에 사용하는 최후의 수단이다. 함수를 단순하게 유지하고 가능하면 함수 오버로드를 사용하지 않는 것이 좋다.


호출 시그니처 호환성

  • 오버로드된 함수의 구현에서 사용되는 구현 시그니처는 매개 변수 타입과 반환 타입에 사용하는 것과 동일하다.
  • 따라서 함수의 오버로드 시그니처에 있는 반환 타입과 각 매개변수는 구현 시그니처에 있는 동일한 인덱스의 매개변수에 할당할 수 있어야 한다.
  • 즉, 구현 시그니처는 모든 오버로드 시그니처와 호환되어야 한다.

[예제]

  • format 함수의 구현 시그니처는 첫 번째 매개변수를 string으로 선언한다.
  • 처음 두 개의 오버로드 시그니처는 string 타입과 호환되지만, 세 번째 오버로드 시그니처의 () => string 타입과는 호환되지 않는다.


Reference

러닝 타입스크립트 (Learning TypeScript)

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.