혼자 적어보는 노트

타입스크립트 프로그래밍 - 4장 본문

Typescript

타입스크립트 프로그래밍 - 4장

jinist 2022. 7. 14. 19:33

 

 

4장 함수

 

4장에서는 함수를 살펴보고 아래와 같은 내용을 다룬다.

 

- 함수를 선언하고 실행하는 방법

- 시그니처 오버로딩

- 다형적 함수

- 다형적 타입 별칭

 

 

# 특별한 상황을 제외하면 매개변수 타입은 추론하지 않는다.

- 문맥을 보고 타입을 추론하는 상황(문맥적 타입화)등 특별한 상황을 제외하면 매개변수 타입을 추론하지 않는다.

* 반환 타입은 자동으로 추론하기때문에 보통 실무에서는 반환타입을 명시하지 않고 추론하도록 한다.

 

 

# 함수에서 this를 사용할 때 첫번째 매개변수로 선언할 수 있다.

function fancyData(this: Date){
	return ${this.getDate()}/${this.getMouth()}
}

fancyDate.call(new Date);
fancyDate() // 에러

함수 내부에서의 this가 의도한대로 동작할 수 있게 타입을 지정해둘 수 있다.

* this는 예약어이기때문에 다른 매개변수와 다르게 처리된다.

 

# 제너레이터로 반환시 반환 타입을 명시할 수 있다.

function* createNumbers(): IterableIterator<number> {
  let n = 0;
  while (1){
    yield n++;
  }
}

let numbers = createNumbers();
numbers.next() // {value: 0, done: false}

 

# 호출 시그니처

(a: number, b: number) => number

위와 같은 함수 타입 문법을 호출 시그니처(Call Signature) 또는 타입 시그니처(Type Signature)라고 한다.

* 여기서 a와 b는 문서화의 용도이기 때문에 타입과 할당에는 아무 영향을 주지 않음

* 함수 호출 시그니처는 바디를 포함하지 않아서 타입을 추론할 수 없기 때문에 반환 타입을 명시해야 함 (number)

 

# 오버로드된 함수

오버로드된 함수는 호출 시그니처가 여러개인 함수를 의미한다.

// 전체 시그니처

const type PersonMaker = {
  (name: string, age: number): Person
}

보통 함수의 타입을 표현할 때 단축 시그니처를 사용하지만 함수 타입의 오버로딩을 할 때는

전체 시그니처를 사용하는 것이 좋다.

 

type PersonMaker = {
  (name: string, age: number, gender: string): Person
  (name: string, gender: string) : Person
}

const personMaker: PersonMaker = (
  name: string,
  ageOrGender: number | string,
  gender?: string
) => {
  
}

이부분이 조금 헷갈렸는데 다시 읽으니 이해가 됐다.

personMaker에는 name, age, gender의 매개변수를 받을 수 있지만

상황에 따라 name과 gender만 들어올 수도 있다.

age가 없을 경우 2번째 매개변수는 age가 될 수도 있고 gender가 될 수도 있기 때문에 직접 선언해주어야 한다.

 

 

함수의 프로퍼티를 만드는데도 전체 시그니처를 사용할 수 있다.

const warnUser = (warning) => {
  if(warnUser.wasCalled){
   return;
  }
  
  warnUser.wasCalled = true;
  alert(warning);
}

warnUser.wasCalled = false;


type WarnUser = {
  (warning: string): void
  wasCalled: boolean
}

WarnUser는 호출할 수 있는 함수임과 동시에 wasCalled를 가지고 있음을 나타낼 수 있다.

 

 

# 제네릭 타입 <T>의 위치

제네릭 타입은 선언 위치에 따라 타입의 범위와 제네릭 타입을 언제 구체 타입으로 한정하는지가 결정된다.

 

1. <T>를 호출 시그니처의 일부로 선언

type Filter = {
  <T>(array: T[], f: (item: T) => boolean): T[]
}

const filter: Filter = (array, f) => {}

 

호출 시그니처의 여는 괄호 바로 앞에 선언하면 T의 범위를 개별 시그니처로 한정한다

(해당 타입의 함수를 실제로 호출할 때 타입을 T로 한정)

 

2. T의 범위를 타입 별칭으로 한정

type Filter<T> = {
  (array: T[], f: (item: T) => boolean): T[]
}

// 위와 같이 선언할 경우

const filter: Filter<number> = (array, f) => {}

// 구체적인 타입 인수를 전달해주어야 한다.

T의 범위를 모든 시그니처로 한정한다.

 

type MyEvent<T> = {
  target: T;
  type: string;
};

let myEvent: MyEvent<HTMLElement | null> = {
  target: document.querySelector('div'),
  type: 'click',
};

위와 같은 제네릭 타입을 사용할 땐 타입이 자동으로 추론되지 않으므로

타입 매개변수를 명시적으로 한정해주어야 한다.

 

 

#T 타입을 가진 U (한정된 다형성)

type Person = {
  name: string;
};

type Student = Person & {
  class: string;
};

type OfficeWorker = Person & {
  salary: number;
};

function updateName<T extends Person>(user: T, name: string): T {
  return {
    ...user,
    name,
  };
}

T는 적어도 Person이어야 한다면 extends를 사용하여 타입을 제한할 수 있다. => name이 무조건 있어야 할 경우.

 

 

# 가변 인수의 개수 정의하기

const call = <T extends unknown[], R>(f: (...args: T) => R, ...args: T): R => {
  //T는 어떤 타입의 배열

  return f(...args);
};

function fill(length: number, value: string): string[] {
  return Array.from({ length }, () => value);
}

call(fill, 10); // X 3개의 인수가 전달되어야 하는데 2개가 전달되어 에러

call(fill, 10, 'a'); // O

인수 배열 타입의 T와 임의의 가변값 R을 사용하여 가변인수의 개수를 정의할 수 있다.

 

 

 

# Promise 타입

promise는 제네릭 타입 매개변수를 명시하여 사용한다.

const promise = new Promise<number>(resolve => 
  resolve(10);
);

promise.then(result => // number로 추론
  result + 3;
)

여기서 제네릭 타입을 명시하지 않으면 result는 {}로 추론한다.

 

 

 

# 제네릭에 기본값을 추가할 수 있다.

기본 값을 정의해두면 사용할 때 타입 매개변수를 수동으로 작성하지 않아도 된다.

type MyEvent<T = HTMLElement> = {
  target: T;
  type: string;
};

let myEvent: MyEvent = { // 여기서 수동으로 작성하지 않아도 됨
  target: document.querySelector('div'),
  type: 'click',
};

 

 

# 기본타입을 가진 제네릭은 기본타입을 갖지 않는 제네릭의 뒤에 위치해야한다.

 // X
type MyEvent<U extends HTMLElement = HTMLElement, T extends string> = {
  // 에러 발생 : Required type parameters may not follow optional type parameters.
  target: T;
  type: U;
};

 // O
type MyEvent<T extends string, U extends HTMLElement = HTMLElement> = { 
  target: T;
  type: U;
};

 

 

Comments