More on Functions[Typescript]

SnowCat·2023년 2월 14일
0

Typescript - Handbook

목록 보기
5/9
post-thumbnail

함수 타입 표현식

  • 화살표 함수와 유사한 방식을 통해 타입 표현 가능
  • 매개변수 타입이 지정되지 않으면 암묵적으로 any가 됨
function greeter(fn: (a: string) => void) {
  fn("Hello, World");
}
 
function printToConsole(s: string) {
  console.log(s);
}

// any => void
function printToConsole(string) {
  console.log(string);
}

// 타입 별칭 사용도 가능
type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
  // ...
}


greeter(printToConsole);

호출 시그니처

  • 자바스크립트에서 함수들은 프로퍼티를 가질 수 있음
  • 함수 타입 표현식에서는 프로퍼티 정의가 불가능 하기 때문에 객체 타입에 호출 시그니처를 사용해 표현함
type DescribableFunction = {
  description: string;
  (someArg: number): boolean;
};
// 문법이 앞선 경우와 다름
function doSomething(fn: DescribableFunction): void {
  console.log(fn.description + " returned " + fn(6));
}

구성 시그니처

  • 함수는 new연산자를 통해서 호출 가능하며, 이는 주로 새로운 객체를 생성하는데 사용되기 때문에 타입스크립트에서는 생성자로 간주됨
  • 호출 시그니처 앞에 new 키워드를 붙임으로서 구성 시그니처를 작성 가능
type SomeConstructor = {
  new (s: string): SomeObject;
};
function fn(ctor: SomeConstructor) {
  return new ctor("hello");
}
  • 호출 시그니처와 구성 시그니처를 같은 타입에서 결합시킬수도 있음
// Date의 경우 new가 있든 없든 호출 가능
interface CallOrConstruct {
  new (s: string): Date;
  (n?: number): number;
}

제네릭 함수

  • 제네릭을 사용해 함수의 타입을 지정해줄수도 있음
  • 실제 들어가는 요소에 맞춰 타입스크립트가 제네릭 위치의 타입추론을 해줌
function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

const s = firstElement(["a", "b", "c"]); //string
const n = firstElement([1, 2, 3]); //number
const u = firstElement([]);//undefined

타입 제한 조건

  • extends를 활용하면 제네릭 타입에 특정한 속성을 가진 타입들만 들어오게 할 수 있음
  • 타입스크립트가 타입추론을 통해 들어갈 수 없는 타입이 들어오면 오류를 출력함
function longest<Type extends { length: number }>(a: Type, b: Type) {
  if (a.length >= b.length) {
    return a;
  } else {
    return b;
  }
}
 
const longerArray = longest([1, 2], [1, 2, 3]);
const longerString = longest("alice", "bob");
// Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
const notOK = longest(10, 100);

타입 인수 명시

  • 타입스크립트가 타입추론을 하지 못하는 경우 수동으로 타입을 명시해야함
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
  return arr1.concat(arr2);
}

//Type 'string' is not assignable to type 'number'.
const arr = combine([1, 2, 3], ["hello"]);

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

좋은 제네릭 함수를 작성하는 법

  1. 가능한 한 타입 매개변수를 제약하지 말 것
// good
function firstElement1<Type>(arr: Type[]) {
  return arr[0];
}

// bad, 호출 시점에 extends any[]를 보고 타입을 any로 추론함
function firstElement2<Type extends any[]>(arr: Type) {
  return arr[0];
}
  1. 가능한 한 타입 매개변수는 최소한으로 사용할 것
// good
function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] {
  return arr.filter(func);
}

// bad, 무의미한 Func 타입 사용
function filter2<Type, Func extends (arg: Type) => boolean>(
  arr: Type[],
  func: Func
): Type[] {
  return arr.filter(func);
}
  1. 함수가 꼭 제네릭이 필요한 것인지 생각해볼 것
// good
function greet(s: string) {
  console.log("Hello, " + s);
}

// bad, 제네릭이 필요없는 함수
function greet<Str extends string>(s: Str) {
  console.log("Hello, " + s);
}

선택적 매개변수

  • 타입스크립트에서 인자가 선택적으로 필요한 경우 ?로 표시함
// name: string | undefined
function printHello(name?: string) {
  console.log(`hello! ${name ?? ""}`)
}
  • 인자에 기본값이 필요하다면 기본값 역시 제공 가능함
// name: string
function printHello(name = "Typescript") {
  console.log(`hello! ${name}`)
}

printHello(); //"hello! Typescript"
  • 콜백함수에서는 사용하지 말것
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i], 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()); // Object is possibly 'undefined'.
});

함수 오버로드

  • 함수 하나가 여러 인수의 개수, 타입을 받아야 하는 경우, 여러개의 함수 시그니처를 작성한 다음 함수 본문을 작성하면 됨
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); //ok
const d2 = makeDate(5, 5, 5);  //ok
const d3 = makeDate(1, 3);  // No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
  • 함수 본문을 작성하기 위해 사용된 시그니처는 외부에서 보이지 않음에 주의
function fn(x: string): void;
function fn(): void; // 매개변수 없이 함수를 호출하려면 구현 시그니처 이외에 오버로드 시그니처 명시 필요
function fn() {
  // ...
}
fn(); //Expected 1 arguments, but got 0.
  • 오버로드 시그니처를 기반으로 구현 시그니처를 작성할 때 구현 시그니처는 모든 오버로드된 시그니처와 호환되야 함
function fn1(x: boolean): void;
function fn1(x: string): void;

function fn2(x: string): string;
function fn2(x: number): boolean;

// This overload signature is not compatible with its implementation signature.

function fn1(x: boolean) {} // x: boolen | string

function fn2(x: string | number) {
  return "oops";  // string => string, number => boolean 식으로 수정 필요
}
  • 유니언 타입이 사용가능할 때는 유니언 타입을 사용하는 것을 권장
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 ? "hello" : [0]); // 타입을 number[] | "hello"(string) 로 인식

// 유니언 타입을 사용하면 더 깔끔한 코드로 문제 해결 가능
function len(x: any[] | string) {
  return x.length;
}

함수 내에서의 this 선언

  • 타입스크립트는 this를 코드 흐름 분석을 통해 추론함
const user = {
  id: 123,
 
  admin: false,
  becomeAdmin: function () {
    this.admin = true; //this -> user에 들어오는 객체
  },
};
  • 타입스크립트에서는 this의 타입 지정이 가능함
interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}
 
const db = getDB();
const admins = db.filterUsers(function (this: User) {
  return this.admin;
});
const admins = db.filterUsers(() => this.admin); // 화살표 함수에서는 this 할당 불가능

알고있어야 하는 타입

  • void: 값을 반환하지 않는 함수의 반환값, 자바스크립트에서는 undefined를 반환하지만, 타입스크립트에서는 둘을 다르게 취급함
  • object: string, number, boolen 등의 원시값이 아닌 모든 값을 지칭함, 빈 객체 타입 { }와도 다르고, Object(전역 타입)와도 다름에 주의
    모든 객체의 최상위에는 Object.prototype가 있고 모든 객체는 프로토타입 체인을 통해 Object.prototype에 도달 가능
    함수 역시 객체의 일종이기 때문에 함수의 타입은 object로 처리됨
  • never: 값을 절대 반환하지 않으며, 예외가 발생하거나, 프로그램이 종료되거나, 유니온에 아무것도 남아있지 않은 등 정상적인 방법으로 해당 부분에 도달 불가능한 경우를 의미함
  • Function - 전역 타입으로, bind, call, aplly, 함수값에 있는 다른 프로퍼티를 설명할 때 사용
    any를 반환하는 것으로 여겨지기 때문에 사용하지 않는 것을 권장함

나머지 매개변수와 인수

  • 배열의 곱셈처럼 정해지지 않은 개수의 인수를 받아야 하는 경우, 나머지 매개변수를 사용해 표현 가능
  • 나머지 매개변수에 대한 타입은 암묵적으로는 any가 아닌 any[]가 되며, 타입 표현식은 Array<T>, T[], 튜플 타입으로 표현해야 함
function multiply(n: number, ...m: number[]) {
  return m.map((x) => n * x);
}

const a = multiply(10, 1, 2, 3, 4); //[10, 20, 30, 40]
  • 반대로 spread 구문을 사용해 여러 인수를 제공할 수도 있음
  • 이 경우 타입스크립트는 길이가 정해지지 않은 배열로 타입을 추론하게 됨
const args = [8, 5]; //number[]
// A spread argument must either have a tuple type or be passed to a rest parameter.
const angle = Math.atan2(...args);
  
const args = [8, 5] as const; // 길이가 2인 튜플
const angle = Math.atan2(...args); //ok

매개변수 구조 분해

  • 구조분해 할당을 사용한 인자들의 타입표현은 분해구문 뒤에 위치하게 됨
// 1. 각각의 인자의 타입을 지정
function sum({ a, b, c }: { a: number; b: number; c: number }) {
  console.log(a + b + c);
}

// 2. 타입을 따로 지정해 사용
type ABC = { a: number; b: number; c: number };
function sum({ a, b, c }: ABC) {
  console.log(a + b + c);
}

void 반환 타입

  • void 반환타입이라고 해서 함수가 반환값이 없다는 것을 보증해주지는 않음
  • 이를 사용하는 값들의 타입은 void로 유지되기 때문에 함수가 리터럴로 사용될 때 사용하지 말 것
type voidFunc = () => void;

const f1: voidFunc = () => {
  return true;
};

const f2: voidFunc = () => true;

const f3: voidFunc = function () {
  return true;
};

// 전부 true 출력
console.log(f1());
console.log(f2());
console.log(f3());

출처:
https://www.typescriptlang.org/ko/docs/handbook/2/functions.html

profile
냐아아아아아아아아앙

0개의 댓글