기존 블로그에 작성했던 스터디 포스트를 이전한 글입니다.
해당 포스트는
TypeScript를 학습하며 정리한 내용에 대한 포스트입니다.
🌈 5. 함수
📖 5.1 함수 매개변수
명시적 타입 정보가 선언되지 않으면 절대 타입을 알 수 없다.
타입스크립트가 이를 any 타입으로 간주하며 매개변수의 타입은 무엇이든 될 수 있다.
변수와 마찬가지로 타입스크립트를 사용하면
Type Annotation으로 함수 매개변수의 타입을 선언 할 수 있다.코드를 유효한 TS 구문으로 만들기 위해 함수 매개변수에 적절한
Type Annotation을 추가할 필요는 없다.타입 오류로 오류를 알리지만 이미 시작 된 자바스크립트는 계속 실행된다.
1
2
3
4
5
6
7
function buildName(firstName: string, lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // 오류, 너무 적은 매개변수
let result2 = buildName("Bob", "Adams", "Sr."); // 오류, 너무 많은 매개변수
let result3 = buildName("Bob", "Adams"); // 정확함
5.1.1 필수 매개변수
자바스크립트에서는 인수의 수와 상관없이 함수를 호출 할 수 있다.
반면 타입스크립트에서는 함수에 선언된 모든 매개변수가 필수라고 가정한다
그렇기 때문에 함수가 잘못된 수의 인수로 호출되면 오류로 이의를 제기한다.
함수에 필수 매개변수를 제공하도록 강제하면 예상되는 모든 인숫값을 함수내에 존재하도록 만들어 타입 안정성 강화한다.
5.1.2 선택적 매개변수
자바스크립트에서 함수 매개변수가 제공되지 않으면 함수 내부의 인숫값은
undefined으로 기본값이 설정된다.매개변수를 제공할 필요가 없을 때도 있고,
undefined값을 위해 의도적으로 사용할 수 있다.타입스크립트에서 이러한 매개변수에 인수를 제공하지 못하는 경우, 타입 오류를 보고 하지 않으려면
?:를 사용 매개변수가 선택적이라고 표시할 수 있다.함수 호출에 선택적 매개변수를 제공할 필요는 없다.
선택적 매개변수에는 항상
| undefined가 유니언 타입으로 추가되어 있다.
1
2
3
4
5
6
7
8
9
10
11
function announceSong(song:string, singer?:string) {
console.log(song);
if (singer) {
console.log(singer);
}
console.log(singer);
}
announceSong("cookie");
announceSong("cookie", undefined);
announceSong("cookie", "NewJeans");
🍀 ?:와 | undefined의 차이
?:는 선택적 매개변수는 항상 암묵적으로undefined가 될 수 있다.|undefined는 값이 명시적으로undefined일지라도 제공되어야한다.함수에서 사용되는 모든 선택적 매개변수는 마지막 매개변수여야 한다.
1
2
function announceSong(song?:string, singer : string) {
// ❌ Error : A required parameter cannot follow an optional parameter.
5.1.3 기본 매개변수
자바스크립트에서 선택적 매개변수를 선언할 때
=와 같이 값이 포함된 기본값을 제공할 수 있다.선택적 매개변수에는 기본적으로 값이 제공되기 때문에 타입스크립트에서 암묵적으로 함수 내부에
|undefined유니언 타입이 추가된다.타입스크립트의
Type Inference은 초기 변숫값과 마찬가지로 기본 함수 매개변수에 대해서도 유사하게 작동한다.매개변수에 기본값이 있고
Type Annotation이 없는 경우, 기본 값을 기반으로 매개 변수 타입 유추한다.
1
2
3
4
5
6
7
8
9
10
11
// (parameter) rating:number
function rateSong(song: string, rating = 0) {
console.log(`${song} gets ${rating}/5 star!`)
}
rateSong("Cookie"); // ✅
rateSong("Cookie", 5); // ✅
rateSong("Cookie", undefined) // ✅
rateSong("Header", "100")
// ❌ Error: Argument of type 'string' is not assignable to parameter of type 'number'.
rating은number타입으로Inference되지만, 함수를 호출하는 코드에서 선택적number | undefined가 된다.
5.1.4 나머지 매개변수
자바스크립트의 일부 함수는 임의의 수의 인수로 호출할 수 있도록 만들어진다.
spread operator는 함수 선언의 마지막 매개변수에 위치하고, 매개변수에서 시작해 함수에 전달된 “나머지” 인수가 모두 단일 배열에 저장되는 것을 의미한다.타입스크립트는 이러한 나머지 매개변수의 타입을 일반 매개변수와 유사하게 선언할 수 있다.
인수 배열을 나타 내기 위해 끝에
[]구문이 추가해야 한다.
1
2
3
4
const mapping = (...args: number) => args.map(Number);
// ❌ Error : A rest parameter must be of an array type.
const mapping2 = (...args: number[]) => args.map(Number);
📖 5.2 반환 타입
타입스크립트는
지각적(알아서 깨닫는. 또는 그런 것.)이다.함수가 반환할 수 있는 가능한 모든 값을 이해하면 함수가 반환하는 타입을 알 수 있다.
1
2
3
4
// function singSong(songs:string[]):number
function singSong(songs:string[]){
return songs.length
}
위의 예시에서
numbertype을return하는 것으로 추론한다.함수에 다른 값을 가진 여러 개의 반환문을 포함하고 있다면, 타입스크립트는 반환 타입을 가능한 모든 반환 타입의 조합으로 유추된다.
1
2
3
4
5
6
// function getSongAt(songs: string[], index: number) : string | undefined
function getSongAt(songs: string[], index: number) {
return index < songs.length
? songs[index]
: undefined
}
5.2.1 명시적 반환 타입
변수와 마찬가지로 타입 애너테이션을 사용해 함수의 반환 타입을 명시적으로 선언하지 않는 것이 좋다.
그러나 함수에서 반환 타입을 명식적으로 선언하는 방식이 매우 유용할 때가 있다.
가능한 반환값이 많은 함수가 항상 동일한 타입의 값을 반환하도록 강제한다.
타입스크립트는 재귀 함수의 반환 타입을 통해 타입을 유추하는 것을 거부한다.
수백 개 이상의 타입스크립트 파일이 있는 매우 큰 프로젝트에서 타입스크립트의 타입 검사 속도를 높일 수 있다.
반환문이 반환 타입으로 할당할 수 없는 값을 반환하는 경우 할당 가능성 오류 표시
- 함수 선언 일 때
{앞에 배치
1
2
3
function test(songs:string[], count =0): number{
return songs.length ? test(songs.slice(1), count +1) : count;
}
- 화살표 함수 일 때
=>앞에 배치
1
2
const singSongRecursive = (songs: string[], count =0): number =>
songs.length ? singSongRecursive(songs.slice(1), count +1) : count;
📖 5.3 함수 타입
자바스크립트에서는 함수를 값으로 전달할 수 있다.
즉 함수를 가지기 위한 매개변수 또는 변수의 타입을 선언하는 방법이 필요하다.
함수 타입 구문은 화살표 함수와 유사하지만 함수 본문 대신 타입이 있다.
1
2
//매개 변수가 없고 string 타입을 리턴하는 함수
let nonthingInGivesString: () => string;
1
2
// string[] 매개변수, count 선택적 매개변수 , number 값을 반환하는 함수
let inputAndOutput:(songs: string[], count?: number) => number;
함수 타입은 콜백 매개변수를 설명하는데 자주 사용된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const songs = ["Juice", "Shake It Off", "What's Up"]
//getSongAt 매개변수의 타입을 index: number를 받고 string을 반환 하는 함수로 선언
function runOnSongs(getSongAt: (index: number) => string){
for(let i = 0; i < songs.length; i += 1){
console.log(getSongAt(i));
}
}
function getSongAt(index: number){
return `${songs[index]}`
}
//getSongAt을 전달 시 일치
runOnSongs(getSongAt); // ✅
function logSong(song: string){
return `${song}`
}
//string 타입을 사용하여 반환값을 가져오는데 실패
runOnSongs(logSong);
// ❌ Error : Argument of type '(song: string) => string' is not
// assignable to parameter of type '(index: number) => string'.
5.3.1 함수 타입 괄호
함수 타입은 다른 타입이 사용되는 모든 곳에 배치 가능하다(유니언 타입도 포함).
유니언 타입의
Annotation함수 반환 위치를 나타내거나 유니언 타입을 감싸는 부분을 표시할 때괄호를 사용한다.
1
2
3
4
5
//타입은 string | undefined 유니언을 반환하는 함수
let returnStringOrundefinde: () => string | undefined;
// 타입은 undefined 이거나 string을 반환하는 함수
let test: (() => string) | undefined;
5.3.2 매개변수 타입 추론
- 타입스크립트는 선언된 타입의 위치에 제공된 함수의 매개변수 타입을 유추할 수 있다.
1
2
3
4
5
6
let singer: (song: string) => string;
//singer 변수는 string의 매개 변수를 갖는 함수로 알려져있다.
// song : string
singer = function (song) {
return song.toUpperCase();
}
- 함수를 매개변수로 갖는 함수에 인수로 전달된 함수는 해당 매개변수 타입도 잘 유추할 수 있다.
1
2
3
4
5
6
7
const songs = ["call", "me", "hi"]
//song : string;
//index : number;
songs.forEach((song,index) => {
console.log(song)
console.log(index)
})
5.3.3 함수 타입 별칭
함수 타입에서도 동일하게 타입 별칭 사용할 수 있다.
매개변수도 함수 타입을 참조하는 별칭을 사용 가능.
타입 별칭은 함수 타입에 유용하다.
- 반복적으로 작성하는 매개변수, 반환 타입을 갖는 코드 공간 절약할 수 있다.
1
2
3
4
5
6
7
8
type StringToNumber = (input: string) => number;
let StringToNumber: StringToNumber;
StringToNumber = (input) => input.length;
StringToNumber = (input) => input.toUpperCase();
//❌ Error : Type 'string' is not assignable to type 'number'.
📖 5.4 그 외 반환 타입
- void
- never
5.4.1 void 반환 타입
일부 함수는 어떤 값도 반환하지 않는다.
return이 없는 함수이거나 값을 반환 하지 않는return을 가진 함수일 경우타입스크립트는
void키워드를 사용해 반환 값이 없는 함수의 반환 타입을 확인 가능
1
2
3
4
5
6
7
8
function logSong(song: string | undefined): void{
if(!song){
return;
}
console.log(song)
return true;
//❌ Error : Type 'boolean' is not assignable to type 'void'.
}
- void 사용 시 함수에서 반환되는 모든 값은 무시된다.
1
2
3
4
5
6
7
8
let songLogger: (song: string) => void;
songLogger = (song) => {
console.log("${songs}");
}
songLogger("CookieCookie"); // Ok;
// 값을 반환하지 않음
자바스크립트의 함수는 실젯값이 반환되지 않으면 기본으로 모두
undefined를 반환한다.void는undefined와 동일하지 않다.void는 함수의 반환 타입이 무시 된다는 것을 의미한다undefined는 반환되는 리터럴 값이다.undefined를 포함하는 대신void타입의 값을 할당 시 오류 발생
1
2
3
4
5
6
function returnsVoid(){
return;
}
let lazyValue: string| undefined;
lazyValue = returnsVoid();
//❌ Error : Type 'void' is not assignable to type 'string | undefined'.
undefinded와void를 구분해서 사용하는 편히 좋다.특히
void를 반환하도록 선언된 타입 위치에 전달된 함수가 반환된 모든 값을 무시하도록 설정할 때 유용하다.
1
2
3
4
5
6
7
8
9
10
const records: string[] = []
function saveRecords(newRecords: string[]) {
newRecords.forEach(record => records.push(record));
saveRecords(["1", "Come On", "Cookie"])
// forEach는 void를 반환하는 콜백을 받는다.
// forEach에 제공 되는 함수는 원하는 모든 값을 반환할 수 있다.
// records.push(record)는 number를 반환 하지만
// 여전히 newRecords.forEach에 전달된 화살표 함수에 대한 반환값이 허용
}
void타입은 자바스크립트가 아닌 함수의 반환 타입을 선언하는데 사용하는 타입스크립트 키워드다.void타입은 함수의 반환값이 자체적으로 반환될 수 있는 값도 아니고, 사용하기 위한 것도 아니라는 표시라는 것을 기억하자.
5.4.2 never 반환 타입
일부 함수는 값을 반환하지 않을 뿐만 아니라 반환할 생각도 없다.
never반환 함수는(의도적)오류를 발생 시키거나 무한 루프를 실행하는 함수함수가 절대 반환하지 않도록 의도하려면
:never타입Annotation을 추가해 해당 함수를 호출한 후 모든 코드가 실행되 않음을 나타낸다.
1
2
3
4
5
6
7
8
9
10
11
12
//fail 함수는 오류만 발생 시키므로
//param 타입을 string으로 좁혀서 타입스크립트의 제어 흐름 분석을 돕는다!
function fail(message: string): never{
throw new Error(message)
}
function workWithUnsafeParam(param: unknown){
if(typeof param !== "string"){
fail("${typeof Param}")
}
param.toUpperCase(); // ✅ param은 string 타입
}
🍀
never와void의 차이
void는 아무것도 반환하지 않는 함수를 위한 것
never은 절대 반환하지 않는 함수를 위한 것
📖 5.5 함수 오버로드
일부 자바스크립트 함수는 선택적 매개변수와 나머지 매개변수만으로 표현할 수 없는 매우 다른 매개변수들로 호출될 수 있다.
이러한 함수를 오버로드 시그니처 overload signature라고 불리는 타입스크립트 구문으로 설명 가능하다.
하나의 최종 «strong style=”color:#ff6600”>구현 시그니처 implementation</strong>와 함수의 본문 앞에 서로 다른 버전의 함수 이름, 매개변수, 반환 타입을 여러 번 선언한다.
오버로드된 함수 호출에 대해 구문 오류를 생성할지 여부를 결정할 때 타입스크립트는 함수의 오버로드 시그니처만 확인한다.
구현 시그니처는 함수의 내부 로직에서만 사용된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 오버로드 시그니처
function creatDate(timestamp: number): Date;
function creatDate(month: number, day: number, year: number): Date;
// 구현 시그니처
function creatDate(monthOrTimestamp: number, day?: number, year?: number) {
return day === undefined || year === undefined
? new Date(monthOrTimestamp)
: new Date(year, monthOrTimestamp, day);
}
creatDate(554356800); // ✅
creatDate(7, 27, 1987); // ✅
creatDate(4, 1);
// ❌ Error : No overload expects 2 arguments,
// but overloads do exist that expect either 1 or 3 arguments.
- 다른 파일 시스템 구문처럼 오버로드 시그니처도 지워진다.
🚨 함수 오버로드는 복잡하고 설명하기 어려운 함수 타입에 사용하는 최후의 수단이므로 함수를 단순하게 유지하고 가능하면 함수 오버로드를 사용하지 않는 것이 좋다.
5.5.1 호출 시그니처 호환성
오버로드된 함수의 구현에서 사용되는 구현 시그니처는 매개변수 타입과 반환 타입에 사용하는 것과 동일하다.
함수의 오버로드 시그니처에 있는 반환 타입과 각 매개변수는 구현 시그니처에 있는 동일한 인덱스의 매개변수에 할당할 수 있어야 한다.
구현 시그니처는 모든 오버로드 시그니처와 호환 해야한다
1
2
3
4
5
6
7
8
9
function format(data:string):string // ✅
function format(data:string, needle:string,haystack:string):string // ✅
function format(getData:()=>string):string;
// ❌ Error : This overload signature is not compatible with its implementation signature.
function format(data:string, needle?:string,haystack?:string){
return needle && haystack ? data.replace(needle,haystack) : data;
}
📚 레퍼런스
Goldberg, et al. 러닝 타입스크립트 / 조시 골드버그 지음 ; 고승원 옮김, 2023.