ReactNextCentral

함수 타입

Published on
타입스크립트에서 함수를 정의하고 사용하는 다양한 방법, 제네릭 함수, 함수 오버로드, 선택적 매개변수, 나머지 매개변수 및 함수 타입에 관련된 여러 타입(`void`, `object`, `unknown`, `never`, `Function`)을 포함하여 함수와 관련된 타입스크립트의 기능들을 소개합니다.
Table of Contents

함수에 대해 더 알아보기

함수는 로컬 함수, 다른 모듈에서 가져온 함수, 클래스의 메서드이든 간에 모든 애플리케이션의 기본 구성 요소입니다. 또한 값이며, 다른 값과 마찬가지로 타입스크립트는 함수가 어떻게 호출될 수 있는지에 대해 여러 방법으로 설명할 수 있습니다. 함수를 설명하는 타입을 작성하는 방법에 대해 알아봅시다.

함수 타입 표현식

함수를 설명하는 가장 간단한 방법은 함수 타입 표현식을 사용하는 것입니다. 이 타입들은 화살표 함수와 문법적으로 유사합니다.

function greeter(fn: (a: string) => void) {
  fn("안녕, 세상");
}
 
function printToConsole(s: string) {
  console.log(s);
}
 
greeter(printToConsole);

(a: string) => void 구문은 "하나의 매개변수 a를 가지며 이는 string 타입이고, 반환 값이 없는 함수"를 의미합니다. 함수 선언과 마찬가지로 매개변수 타입이 지정되지 않으면, 그것은 암묵적으로 any입니다.

매개변수 이름은 필수입니다. (string) => void 함수 타입은 "any 타입의 string이라는 이름의 매개변수를 가진 함수"를 의미합니다!

물론, 타입 별칭을 사용하여 함수 타입에 이름을 지정할 수 있습니다.

type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
  // ...
}

호출 시그니처

자바스크립트에서 함수는 호출 가능할 뿐만 아니라 속성도 가질 수 있습니다. 그러나 함수 타입 표현식 문법은 속성을 선언하는 것을 허용하지 않습니다. 속성이 있는 호출 가능한 것을 설명하고 싶다면, 객체 타입에 호출 시그니처(signature)를 작성할 수 있습니다.

type DescribableFunction = {
  description: string;
  (someArg: number): boolean;
};
function doSomething(fn: DescribableFunction) {
  console.log(`${fn.description}가 6을 반환함: ${fn(6)}`);
}
 
function myFunc(someArg: number) {
  return someArg > 3;
}
myFunc.description = "기본 설명";
 
doSomething(myFunc);

함수 타입 표현식과 비교할 때 문법이 약간 다릅니다. 매개변수 목록과 반환 타입 사이에 => 대신 :을 사용합니다.

생성자 시그니처

자바스크립트 함수는 new 연산자로 호출될 수도 있습니다. 타입스크립트에서 이러한 함수를 생성자라고 부릅니다. 보통 새 객체를 생성하기 때문입니다. 호출 서명 앞에 new 키워드를 추가함으로써 생성자 시그니처를 작성할 수 있습니다.

type SomeConstructor = {
  new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
  return new ctor("안녕");
}

자바스크립트의 Date 객체와 같은 일부 객체는 new가 있거나 없이 호출될 수 있습니다. 호출 시그니처와 생성 시그니처를 같은 타입에서 임의로 결합할 수 있습니다.

interface CallOrConstruct {
  (n?: number): string;
  new (s: string): Date;
}

제네릭 함수

입력의 타입이 출력의 타입과 관련이 있거나 두 입력의 타입이 어떤 방식으로든 연관되어 있는 함수를 작성하는 것은 일반적입니다. 잠시, 배열의 첫 번째 요소를 반환하는 함수를 고려해 봅시다.

function firstElement(arr: any[]) {
  return arr[0];
}

이 함수는 일을 잘 수행하지만, 불행히도 반환 타입이 any입니다. 함수가 배열 요소의 타입을 반환하는 것이 더 좋을 것입니다.

타입스크립트에서는 두 값 사이의 대응 관계를 설명하고자 할 때 제네릭을 사용합니다. 함수 시그니처에서 타입 매개변수를 선언함으로써 이를 수행합니다.

function firstElement<Type>(arr: Type[]): Type | undefined {
  return arr[0];
}

이 함수에 타입 매개변수 Type을 추가하고 두 곳에서 사용함으로써, 함수의 입력(배열)과 출력(반환 값) 사이의 연결을 만들었습니다. 이제 호출하면 더 구체적인 타입이 나옵니다.

// s는 'string' 타입입니다
const s = firstElement(["a", "b", "c"]);
// n는 'number' 타입입니다
const n = firstElement([1, 2, 3]);
// u는 'undefined' 타입입니다
const u = firstElement([]);

추론

이 예제에서 Type을 명시적으로 지정할 필요가 없었음에 주목하세요. 타입은 타입스크립트에 의해 자동으로 선택되어 추론되었습니다.

또한 여러 타입 매개변수도 사용할 수 있습니다. 예를 들어, 독립 실행형 map 함수는 다음과 같이 보일 것입니다.

function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
  return arr.map(func);
}
 
// 매개변수 'n'은 'string' 타입입니다
// 'parsed'는 'number[]' 타입입니다
const parsed = map(["1", "2", "3"], (n) => parseInt(n));

이 예제에서 타입스크립트는 주어진 문자열 배열로부터 Input 타입 매개변수의 타입을 추론할 수 있었을 뿐만 아니라, 함수 표현식의 반환 값(number)에 기반하여 Output 타입 매개변수의 타입도 추론할 수 있었습니다.

제약 조건

모든 종류의 값에 대해 작동할 수 있는 제네릭 함수를 몇 가지 작성했습니다. 때때로, 두 값을 연관시키고 싶지만 특정 값의 부분 집합에서만 작업할 수 있습니다. 이 경우, 타입 매개변수가 받아들일 수 있는 타입의 종류를 제한하기 위해 제약 조건을 사용할 수 있습니다.

두 값 중 더 긴 값을 반환하는 함수를 작성해 봅시다. 이를 위해서는 length 속성이 숫자인 것이 필요합니다. extends 절을 작성하여 타입 매개변수를 그 타입으로 제한함으로써 제약을 걸 수 있습니다.

function longest<Type extends { length: number }>(a: Type, b: Type) {
  if (a.length >= b.length) {
    return a;
  } else {
    return b;
  }
}
 
// longerArray는 'number[]' 타입입니다
const longerArray = longest([1, 2], [1, 2, 3]);
// longerString은 'alice' | 'bob' 타입입니다
const longerString = longest("alice", "bob");
// 오류! 숫자에는 'length' 속성이 없습니다
const notOK = longest(10, 100);

이 예제에서 주목해야 할 몇 가지 흥미로운 점이 있습니다. longest의 반환 타입을 타입스크립트가 추론하게 했습니다. 반환 타입 추론은 제네릭 함수에서도 작동합니다.

Type{ length: number }로 제한했기 때문에 ab 매개변수의 .length 속성에 접근할 수 있었습니다. 타입 제약이 없었다면, .length 속성 없이 다른 타입일 수 있었기 때문에 이러한 속성에 접근할 수 없었을 것입니다.

longerArraylongerString의 타입은 인수를 기반으로 추론되었습니다. 제네릭은 동일한 타입을 가진 두 개 이상의 값을 관련시키는 것에 관한 것임을 기억하세요!

마지막으로, 우리가 원하는 대로, longest(10, 100) 호출은 숫자 타입에 .length 속성이 없기 때문에 거부되었습니다.

제약된 값 다루기

제네릭 제약을 다룰 때 흔히 발생하는 오류가 있습니다.

function minimumLength<Type extends { length: number }>(
  obj: Type,
  minimum: number
): Type {
  if (obj.length >= minimum) {
    return obj;
  } else {
    return { length: minimum };
  }
}

이 함수가 정상적으로 보일 수 있습니다. Type{ length: number }로 제한되어 있고, 함수는 Type을 반환하거나 해당 제약 조건과 일치하는 값을 반환합니다. 문제는 함수가 전달된 것과 같은 종류의 객체를 반환하겠다고 약속하는 것이 아니라, 단지 제약 조건과 일치하는 어떤 객체를 반환한다는 것입니다. 이 코드가 합법적이라면, 분명히 작동하지 않을 코드를 작성할 수 있습니다.

// 'arr'는 값 { length: 6 }을 가집니다
const arr = minimumLength([1, 2, 3], 6);
// 여기서 크래시가 발생합니다. 배열에는
// 'slice' 메소드가 있지만 반환된 객체에는 없기 때문입니다!
console.log(arr.slice(0));

함수가 전달된 것과 동일한 종류의 객체를 반환하기로 한 약속에 의해, { length: minimum }을 반환하는 것은 Type과 일치하지 않아 오류가 발생합니다. 이런 방식으로 코드를 작성하면, 반환된 객체가 원본 객체와 같은 메서드나 속성을 가지고 있음을 보장할 수 없으므로 문제가 발생할 수 있습니다.

타입 인수 지정하기

타입스크립트는 보통 제네릭 호출에서 의도한 타입 인수를 추론할 수 있지만, 항상 그런 것은 아닙니다. 예를 들어, 두 배열을 결합하는 함수를 작성했다고 가정해 봅시다.

function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
  return arr1.concat(arr2);
}

일반적으로 이 함수를 서로 다른 배열로 호출하는 것은 오류입니다.

const arr = combine([1, 2, 3], ["hello"]);
// 'string' 타입은 'number' 타입에 할당할 수 없습니다.

그러나 이렇게 하려는 의도였다면, Type을 수동으로 지정할 수 있습니다.

const arr = combine<string | number>([1, 2, 3], ["hello"]);

좋은 제네릭 함수 작성 가이드라인

제네릭 함수를 작성하는 것은 재미있으며, 타입 매개변수에 너무 심취하기 쉽습니다. 너무 많은 타입 매개변수를 가지거나 필요하지 않은 곳에 제약을 사용하는 것은 추론을 덜 성공적으로 만들어, 함수를 호출하는 사용자에게 답답함을 줄 수 있습니다.

타입 매개변수를 하위로 내리기

비슷해 보이는 두 가지 방식으로 함수를 작성하는 예입니다.

function firstElement1<Type>(arr: Type[]) {
  return arr[0];
}
 
function firstElement2<Type extends any[]>(arr: Type) {
  return arr[0];
}
 
// a: number (좋음)
const a = firstElement1([1, 2, 3]);
// b: any (나쁨)
const b = firstElement2([1, 2, 3]);

처음 보면 이 두 함수는 동일해 보일 수 있지만 firstElement1이 이 함수를 작성하는 훨씬 더 좋은 방법입니다. 추론된 반환 타입은 Type이지만, firstElement2의 추론된 반환 타입은 any입니다. 이는 타입스크립트가 호출 중에 요소를 해결하기 위해 "대기"하는 대신, arr[0] 표현식을 제약 타입을 사용해 해결해야 하기 때문입니다.

규칙: 가능할 때는 타입 매개변수 자체를 사용하고, 제약을 거는 대신 매개변수를 사용하세요.

더 적은 타입 매개변수 사용하기

다음은 비슷한 두 함수의 또 다른 쌍입니다.

function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
  return arr.filter(func);
}
 
function filter2<Type, Func extends (arg: Type) => boolean>(
  arr: Type[],
  func: Func
): Type[] {
  return arr.filter(func);
}

두 값 사이의 관계가 없는 타입 매개변수 Func를 만들었습니다. 이는 항상 경고 신호인데, 이는 호출자가 타입 인수를 수동으로 지정할 때 아무 이유 없이 추가 타입 인수를 수동으로 지정해야 한다는 것을 의미합니다. Func는 함수를 읽고 이해하는 것을 더 어렵게 만들 뿐입니다!

규칙: 가능한 한 적은 타입 매개변수를 사용하세요

타입 매개변수는 두 번 나타나야 합니다

가끔 함수가 제네릭일 필요가 없다는 것을 잊곤 합니다.

function greet<Str extends string>(s: Str) {
  console.log("안녕, " + s);
}
 
greet("세계");

우리는 더 간단한 버전을 쉽게 작성할 수 있습니다.

function greet(s: string) {
  console.log("안녕, " + s);
}

기억하세요, 타입 매개변수는 여러 값의 타입을 관련짓기 위한 것입니다. 함수 시그니처에서 타입 매개변수가 한 번만 사용된다면, 그것은 아무 것도 관련짓지 않습니다. 이는 추론된 반환 타입에도 포함됩니다; 예를 들어, Strgreet의 추론된 반환 타입의 일부라면, 그것은 인수와 반환 타입을 관련짓기 때문에, 쓰여진 코드에서 한 번만 나타나더라도 실제로는 두 번 사용되는 것입니다.

규칙: 타입 매개변수가 한 곳에만 나타난다면, 실제로 필요한지 다시 한 번 고려해 보세요

선택적 매개변수

자바스크립트의 함수는 종종 변수 개수의 인수를 받습니다. 예를 들어, numbertoFixed 메서드는 선택적 자릿수를 받습니다.

function f(n: number) {
  console.log(n.toFixed()); // 인수 0개
  console.log(n.toFixed(3)); // 인수 1개
}

타입스크립트에서는 매개변수를 ?로 표시하여 선택적으로 만들어 모델링할 수 있습니다.

function f(x?: number) {
  // ...
}
f(); // OK
f(10); // OK

매개변수가 number 타입으로 지정되어 있어도 x 매개변수는 실제로는 number | undefined 타입을 가집니다. 이는 자바스크립트에서 지정되지 않은 매개변수는 undefined 값을 가지기 때문입니다.

매개변수 기본값을 제공할 수도 있습니다.

function f(x = 10) {
  // ...
}

이제 f의 본문에서 xnumber 타입을 가집니다. undefined 인수는 10으로 대체되기 때문입니다. 매개변수가 선택적일 때, 호출자는 항상 undefined를 전달할 수 있습니다. 이는 단순히 "누락된" 인수를 시뮬레이션합니다.

// 모두 OK
f();
f(10);
f(undefined);

콜백에서의 선택적 매개변수

선택적 매개변수와 함수 타입 표현에 대해 배운 후 콜백을 호출하는 함수를 작성할 때 다음과 같은 실수를 쉽게 범할 수 있습니다.

function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], i);
  }
}

index?를 선택적 매개변수로 작성할 때 사람들이 일반적으로 의도하는 것은 다음 두 호출이 모두 합법적이기를 원하는 것입니다.

myForEach([1, 2, 3], (a) => console.log(a));
myForEach([1, 2, 3], (a, i) => console.log(a, i));

실제로 이는 콜백이 한 개의 인수로 호출될 수 있다는 것을 의미합니다. 다시 말해, 함수 정의는 구현이 다음과 같을 수 있음을 나타냅니다.

function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    // 오늘은 인덱스를 제공하고 싶지 않습니다
    callback(arr[i]);
  }
}

이 경우, 타입스크립트는 실제로는 불가능한 오류를 발생시킬 것입니다.

myForEach([1, 2, 3], (a, i) => {
  console.log(i.toFixed());
  // 'i'는 'undefined'일 수 있습니다.
});

자바스크립트에서는 매개변수보다 많은 인수로 함수를 호출하면, 추가 인수는 단순히 무시됩니다. 타입스크립트도 같은 방식으로 동작합니다. 매개변수가 더 적은 함수(동일한 타입의)는 항상 매개변수가 더 많은 함수를 대신할 수 있습니다.

규칙: 콜백의 함수 타입을 작성할 때, 그 인수를 전달하지 않고 함수를 호출하려는 의도가 아니라면 절대로 선택적 매개변수를 작성하지 마세요

함수 오버로드

일부 자바스크립트 함수는 다양한 인수 수와 타입으로 호출될 수 있습니다. 예를 들어, 타임스탬프(하나의 인수) 또는 월/일/년 사양(세 개의 인수)을 사용하여 Date를 생성하는 함수를 작성할 수 있습니다.

타입스크립트에서는 오버로드 시그니처를 작성하여 다양한 방식으로 호출될 수 있는 함수를 지정할 수 있습니다. 이를 위해 함수 시그니처(보통 두 개 이상)를 일련으로 작성한 후에 함수의 본문을 작성합니다.

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);
  }
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
// 오류: 오버로드는 2개의 인수를 예상하지 않지만, 1개 또는 3개의 인수를 예상하는 오버로드가 존재합니다.
const d3 = makeDate(1, 3);

이 예제에서, 우리는 하나의 인수를 받는 오버로드와 세 개의 인수를 받는 오버로드 두 가지를 작성했습니다. 이 첫 두 시그니처는 오버로드 시그니처라고 합니다.

그 다음 호환되는 시그니처를 가진 함수 구현을 작성했습니다. 함수에는 구현 시그니처가 있지만 이 시그니처는 직접 호출될 수 없습니다. 필수 매개변수 뒤에 두 개의 선택적 매개변수를 가진 함수를 작성했음에도 불구하고 이 함수는 두 매개변수로 호출될 수 없습니다!

오버로드 시그니처와 구현 시그니처

이는 흔히 혼란을 일으키는 원인입니다. 종종 사람들은 다음과 같은 코드를 작성하고 왜 오류가 발생하는지 이해하지 못합니다.

function fn(x: string): void;
function fn() {
  // ...
}
// 인수 없이 호출할 수 있을 것으로 예상
fn();
// 예상된 인수 1개, 실제로는 0개를 받았습니다.

다시 말하지만, 함수 본문을 작성하는 데 사용된 시그니처는 바깥쪽에서 "보이지" 않습니다.

구현의 시그니처는 바깥쪽에서 보이지 않습니다. 오버로드된 함수를 작성할 때, 항상 함수 구현 위에 두 개 이상의 시그니처를 가져야 합니다.

구현 시그니처는 또한 오버로드 시그니처와 호환되어야 합니다. 예를 들어, 이 함수들은 구현 시그니처가 오버로드와 올바르게 일치하지 않기 때문에 오류가 있습니다.

function fn(x: boolean): void;
// 인수 타입이 맞지 않음
function fn(x: string): void;
// 이 오버로드 시그니처는 구현 시그니처와 호환되지 않습니다.
function fn(x: boolean) {}
function fn(x: string): string;
// 반환 타입이 맞지 않음
function fn(x: number): boolean;
// 이 오버로드 시그니처는 구현 시그니처와 호환되지 않습니다.
function fn(x: string | number) {
  return "oops";
}

좋은 오버로드 작성하기

제네릭처럼 함수 오버로드를 사용할 때 따라야 할 몇 가지 지침이 있습니다. 이 원칙을 따르면 함수를 호출하기 쉽고 이해하기 쉽고 구현하기 쉬워집니다.

문자열이나 배열의 길이를 반환하는 함수를 고려해 봅시다.

function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
  return x.length;
}

이 함수는 괜찮습니다. 문자열이나 배열로 호출할 수 있습니다. 하지만, 문자열이거나 배열일 수 있는 값을 가지고는 호출할 수 없습니다. 타입스크립트는 함수 호출을 단일 오버로드로만 해결할 수 있기 때문입니다.

len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "안녕" : [0]);
// 오버로드와 일치하는 호출이 없습니다.

모든 오버로드가 같은 인수 수와 반환 타입을 가지고 있기 때문에, 대신 함수의 오버로드되지 않은 버전을 작성할 수 있습니다.

function len(x: any[] | string) {
  return x.length;
}

이 방법이 훨씬 낫습니다! 호출자는 어떤 종류의 값이든 이 함수를 호출할 수 있으며, 추가 보너스로 올바른 구현 시그니처를 찾아내야 하는 부담이 없습니다.

가능할 때는 항상 오버로드 대신 유니온 타입을 가진 매개변수를 선호하세요.

함수에서 this 선언하기

타입스크립트는 코드 흐름 분석을 통해 함수에서 this가 무엇인지 추론할 수 있습니다. 예를 들어, 다음과 같은 경우입니다.

const user = {
  id: 123,
 
  admin: false,
  becomeAdmin: function () {
    this.admin = true;
  },
};

타입스크립트는 user.becomeAdmin 함수의 this가 외부 객체인 user에 해당함을 이해합니다. 많은 경우에 이것만으로 충분하지만, this가 나타내는 객체를 더 통제해야 하는 경우가 많습니다. 자바스크립트 명세는 this라는 이름의 매개변수를 가질 수 없다고 명시하고 있으므로, 타입스크립트는 함수 본문에서 this의 타입을 선언할 수 있는 문법 공간을 사용합니다.

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

이 패턴은 다른 객체가 일반적으로 함수가 호출되는 시점을 제어하는 콜백 스타일 API에서 흔합니다. this의 동작을 얻기 위해 화살표 함수가 아닌 function을 사용해야 한다는 점에 주의하세요.

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(() => this.admin);
// 이 화살표 함수는 'this'의 전역 값을 캡처합니다.
// 'globalThis'의 타입 'typeof globalThis'에 인덱스 서명이 없기 때문에,
// 요소는 암시적으로 'any' 타입을 가집니다.

함수 타입에서 알아야 할 기타 타입들

함수 타입을 다룰 때 자주 등장하는 몇 가지 추가 타입들을 알아보겠습니다. 모든 타입처럼 어디서든 사용할 수 있지만 특히 함수의 맥락에서 관련이 깊은 타입들입니다.

void

void는 값을 반환하지 않는 함수의 반환 값입니다. 함수에 return 문이 없거나, return 문에서 명시적인 값을 반환하지 않을 때 추론되는 타입입니다.

// 추론된 반환 타입은 void
function noop() {
  return;
}

자바스크립트에서 값이 반환되지 않는 함수는 암시적으로 undefined 값을 반환합니다. 하지만 타입스크립트에서 voidundefined는 같지 않습니다. 이 장의 끝에서 더 자세한 내용을 다룹니다.

voidundefined와 같지 않습니다.

object

특별한 타입 object는 원시 타입(문자열, 숫자, bigint, 불리언, 심볼, null, undefined)이 아닌 모든 값을 참조합니다. 이는 빈 객체 타입 { }과도 다르며, 전역 타입 Object와도 다릅니다. Object 대신 항상 object를 사용하는 것이 좋습니다.

자바스크립트에서 함수 값은 객체입니다. 속성을 가지고 있으며, Object.prototype이 프로토타입 체인에 있고, Object의 인스턴스이며, Object.keys를 호출할 수 있습니다. 이러한 이유로 타입스크립트에서 함수 타입은 객체로 간주됩니다.

unknown

unknown 타입은 어떤 값이든 나타낼 수 있습니다. 이는 any 타입과 비슷하지만 unknown 값으로는 아무것도 할 수 없기 때문에 더 안전합니다.

function f1(a: any) {
  a.b(); // OK
}
function f2(a: unknown) {
  a.b(); // 'a'는 'unknown' 타입입니다.
}

함수 타입을 설명할 때 unknown이 유용합니다. 함수 본문에서 아무 값도 사용하지 않으면서 모든 값을 받아들일 수 있는 함수를 설명할 수 있습니다.

반대로, 알려지지 않은 타입의 값을 반환하는 함수를 설명할 수도 있습니다.

function safeParse(s: string): unknown {
  return JSON.parse(s);
}
 
// 'obj'를 주의해서 사용해야 합니다!
const obj = safeParse(someRandomString);

never

일부 함수는 값을 반환하지 않습니다.

function fail(msg: string): never {
  throw new Error(msg);
}

never 타입은 관찰되지 않는 값을 나타냅니다. 반환 타입으로 사용될 때, 함수가 예외를 던지거나 프로그램의 실행을 종료한다는 것을 의미합니다.

never는 타입스크립트가 유니온에서 남은 것이 없다고 판단했을 때도 나타납니다.

function fn(x: string | number) {
  if (typeof x === "string") {
    // 무언가를 합니다
  } else if (typeof x === "number") {
    // 다른 무언가를 합니다
  } else {
    x; // 타입이 'never'입니다!
  }
}

Function

전역 타입 Function은 자바스크립트의 모든 함수 값에 존재하는 bind, call, apply 등의 속성을 설명합니다. 또한, Function 타입의 값은 항상 호출될 수 있으며, 이 호출은 any를 반환합니다.

function doSomething(f: Function) {
  return f(1, 2, 3);
}

이는 타입이 지정되지 않은 함수 호출로, any 반환 타입 때문에 가능한 피하는 것이 좋습니다.

임의의 함수를 받아야 하지만 호출할 의도가 없다면, () => void 타입이 일반적으로 더 안전합니다.

나머지 매개변수와 인수

나머지 매개변수

함수가 고정된 인수 수를 받아들이도록 선택적 매개변수나 오버로드를 사용하는 대신, 나머지 매개변수를 사용하여 무한한 수의 인수를 받아들이는 함수를 정의할 수도 있습니다.

나머지 매개변수는 모든 다른 매개변수 뒤에 나타나며, ... 문법을 사용합니다.

function multiply(n: number, ...m: number[]) {
  return m.map((x) => n * x);
}
// 'a'는 [10, 20, 30, 40] 값을 받습니다
const a = multiply(10, 1, 2, 3, 4);

타입스크립트에서, 이 매개변수들에 대한 타입 주석은 암시적으로 any[]이며, 주어진 모든 타입 주석은 Array<T> 또는 T[] 형식이거나 튜플 타입(나중에 배울 내용)이어야 합니다.

나머지 인수

반대로, 스프레드 문법을 사용하여 반복 가능한 객체(예: 배열)에서 가변적인 수의 인수를 제공할 수 있습니다. 예를 들어, 배열의 push 메서드는 임의의 수의 인수를 받습니다.

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
arr1.push(...arr2);

일반적으로, 타입스크립트는 배열이 불변이라고 가정하지 않습니다. 이로 인해 예상치 못한 동작이 발생할 수 있습니다.

// 추론된 타입은 number[] -- "하나 이상의 숫자가 있는 배열",
// 특정하게 두 숫자가 아님
const args = [8, 5];
const angle = Math.atan2(...args);
// 스프레드 인수는 튜플 타입이어야 하거나 나머지 매개변수에 전달되어야 합니다.

이 상황에 대한 최선의 해결책은 코드에 따라 다르지만, 일반적으로 const 컨텍스트가 가장 직접적인 해결책입니다.

// 2-길이 튜플로 추론됨
const args = [8, 5] as const;
// OK
const angle = Math.atan2(...args);

나머지 인수를 사용하려면 구형 런타임을 대상으로 할 때 downlevelIteration을 활성화해야 할 수도 있습니다.

매개변수 구조 분해

함수의 매개변수로 제공된 객체를 함수 본문 내 하나 이상의 지역 변수로 편리하게 풀어서 사용할 수 있게 하는 매개변수 구조 분해를 사용할 수 있습니다. 자바스크립트에서는 다음과 같이 보입니다.

function sum({ a, b, c }) {
  console.log(a + b + c);
}
sum({ a: 10, b: 3, c: 9 });

객체에 대한 타입 주석은 구조 분해 문법 뒤에 옵니다.

function sum({ a, b, c }: { a: number; b: number; c: number }) {
  console.log(a + b + c);
}

이 방식은 약간 장황해 보일 수 있지만, 여기에서도 명명된 타입을 사용할 수 있습니다.

// 이전 예제와 동일
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
  console.log(a + b + c);
}

함수의 할당 가능성

반환 타입 void

함수의 void 반환 타입은 예상치 못한, 그러나 예상되는 행동을 일으킬 수 있습니다.

void 반환 타입을 가진 문맥적 타이핑은 함수가 무언가를 반환하지 않도록 강제하지 않습니다. 다르게 말하자면, void 반환 타입을 가진 문맥적 함수 타입(type voidFunc = () => void)이 구현될 때, 다른 값을 반환할 수 있지만 그 값은 무시됩니다.

따라서, 다음과 같은 () => void 타입의 구현은 유효합니다.

type voidFunc = () => void;
 
const f1: voidFunc = () => {
  return true;
};
 
const f2: voidFunc = () => true;
 
const f3: voidFunc = function () {
  return true;
};

그리고 이러한 함수 중 하나의 반환값이 다른 변수에 할당될 때, 그 타입은 void를 유지합니다.

const v1 = f1();
 
const v2 = f2();
 
const v3 = f3();

이러한 행동은 Array.prototype.push가 숫자를 반환하고 Array.prototype.forEach 메소드가 void 반환 타입의 함수를 예상함에도 불구하고, 다음 코드가 유효하다는 것을 보장하기 위해 존재합니다.

const src = [1, 2, 3];
const dst = [0];
 
src.forEach((el) => dst.push(el));

한 가지 특별한 경우를 알고 있어야 합니다. 리터럴 함수 정의가 void 반환 타입을 가질 때, 그 함수는 아무 것도 반환하지 않아야 합니다.

function f2(): void {
  // @ts-expect-error
  return true;
}
 
const f3 = function (): void {
  // @ts-expect-error
  return true;
};