TS스터디 이펙티브 item12~13

온호성·2023년 4월 1일
1

😃item 12 함수 표현식에 타입 적용하기

js에서는 함수를 선언할때 함수 문장과 함수 표현식을 사용하는데

function dice(input) { } // 함수 문장
const dice = (input) => { } // 함수 표현식

ts에서는 함수 표현식을 사용하는 것이 좋다.

함수 표현식 장점

1. 함수의 매개변수 부터 반환값까지 전체를 함수 타입으로 선언하여 함수 표현식에 재사용할 수 있다.

type Add = (num: number) => number;
const add: Add = num => { /* ... */ }

2. 불필요한 타입 선언 코드의 반복을 줄인다.

함수의 매개변수부터 반환값까지 전체를 함수 타입으로 선언하여 함수 표현식에 재사용할 수 있다

type BinaryFn = (a: number, b: number) => number;
const add: BinaryFn = (a, b) => a + b;
const sub: BinaryFn = (a, b) => a - b;
const mul: BinaryFn = (a, b) => a * b;
const div: BinaryFn = (a, b) => a / b;

3. 공통 콜백 함수를 위한 타입 선언을 제공할 수 있다

  • 라이브러리는 공통 함수 시그니처를 타입으로 제공하여, 공통 콜백 함수를 위한 타입 선언을 제공한다.
  • 리액트는 함수의 매개변수에 명시하는 MouseEvent 타입 대신에, 함수 전체에 적용할 수 있는 MouseEventHandler 타입을 제공한다.

만약 다른 함수의 시그니처를 참조하려면 typeof fn 을 사용하면 된다.


기존에 있는 함수와 동일한 시그니처 사용에도 함수 표현식이 좋은데

fetch 함수는 요청이 실패했을때 reject(실패) 된 promise 를 반환하지 않는다. 함수를 호출한 곳에서 사용하기 쉽도록 커스텀 함수를 작성할 때 함수 표현식으로 작성하면 기존 타입을 재사용할 수 있다.

const customFetch: typeof fetch = async (input, init) => {
  const response = await fetch(input, init);
  return response.ok ? response : throw new Error('Request failed')
}

-요약-

  • 변수나 반환 값 타입 명시보다 함수 표현식 전체에 타입 구문을 적용하는 것이 좋다.
  • 시그니처를 반복적으로 작성한 코드가 있다면 함수 타입을 분리해내거나 이미 존재하는 타입을 찾아보자.
  • 버리를 직접 만든다면 공통 콜백에 타입을 제공해야 한다.
  • 함수의 시그니처를 찾는다면 typeof fn을 사용한다.

😥아이템 13 타입과 인터페이스의 차이점 알기

ts에서 명명된 타입(named type)을 정의하는 typeinterface 가 있다.
(type과 interface에 접두사로 책에서 T/I를 붙이는 것은 C#에서 영향을 받았는데 ts에서는 지양하는게 좋다고 한다.)

type과 interface 공통점

1. 타입 상태에는 차이가 없다.

type TPerson = {
  name: string,
}
interface IPerson {
  name: string;
}
// 두 타입 모두 상태가 동일하다.
// 두 타입이 지정된 변수에 알려진 객체 리터럴을 할당하려고 하면 동일한 방법으로 타입 체크를 진행한다.

2. 인덱스 시그니처 사용 가능

type TPerson = {
  [key: string]: string,
}
interface IPerson {
  [key: string]: string;
}

3. 함수 타입도 정의 가능

type TFn = (x: number) => string;
interface IFn {
  (x: number): string;
}

4. 제네릭이 가능하다.

type TPair<T> = {
  first: T;
  second: T;
}
interface IPair<T> {
  first: T;
  second: T;
}

5. 인터페이스는 타입을 확장할 수 있으며, 타입은 인터페이스를 확장할 수 있다.

interface IStateWithPop extends TState {
  population: number;
}

type TStateWithPop = IState & { population: number; };
// IStateWithPop 와 TStateWithPop 는 동일하다.

💦 단, 인터페이스는 유니온 타입 처럼 복잡한 타입을 확장하지 못한다. 복잡한 타입을 확장하고 싶다면 타입& 를 사용해야한다.

6. 클래스를 구현(implements)할 수 있다.

class StateT implements TState {
  name: string = '';
  capital: string = '';
}

class StateI implements IState {
  name: string = '';
  capital: string = '';
}

type과 interface 차이점

1. 인터페이스는 타입을 확장할 수 있지만, 유니온을 확장할 순 없다.

다음 처럼 복잡한 타입은 인터페이스로 표현할 수 없다.

type NamedVariable = (Input | Output) & { name: string };

즉, 타입은 유니온 (Input | Output) 을 & 로 확장할 수 있지만
인터페이스로 유니온을 확장할 수는 없다.

2. 인터페이스로 튜플 타입을 완벽하게 구현할 수 없다.

타입은 아래 처럼 튜플을 간결하게 표현할 수 있다.

type Pair = [number, number];

하지만 인터페이스로는 아래처럼 비슷하게 구현할 수 는 있지만, Array 고차함수를 사용할 수는 없다.

interface Pair {
  0: number;
  1: number;
  length: 2;
}

(튜플(tuple)이란 자료 구조로, 배열처럼 여러 개의 데이터를 열거하여 담아두는 데에 사용한다.)

3. 인터페이스는 보강(argument)이 가능하다.

인터페이스는 아래 예제처럼 선언 병합(declaration merging) 이 가능하다.
아래의 코드를 보면 인터페이스가 중복으로 선언된 것 같지만 올바른 문법이고 아래처럼 속성을 확장하는 것을 선언병합 이라고 한다.

interface IPerson {
  name: string;
}
interface IPerson {
  age: number;
}
const hee: IPerson = {
  name: 'hee',
  age: 25
} // 정상

선언 병합은 주로 타입 선언 파일에서 사용한다. 선언 병합은 인터페이스에서만 가능하다.

ts는 여러 버전의 js 표준 라이브러리에서 여러 타입을 모아 병합한다. 병합을 통해 다른 인터페이스에 추가되어 하나의 타입에 버전별로 메서드들을 모을 수 있다.


  • API처럼 사용자가 인터페이스를 통해 새로운 필드를 병합해야 하는 경우엔 인터페이스를 사용하고, 간단하고 일관된 타입을 사용한다면 타입을 사용하면 된다.
  • 어지간하면 타입이 좋다.
    보강의 가능성이 있을 경우 (버전) => interface 사용
    API 타입 선언 등 => interface 사용 (새로운 필드 병합)

인터페이스와 타입은 서로 비슷하지만, 사용하는 상황에 따라서 선택해야 한다. 인터페이스는 객체의 구조를 설명하거나 다른 인터페이스와 결합할 때 사용하는 경우가 많은 반면, 타입은 값을 묘사하고, 유니온 타입과 인터섹션 타입과 같은 고급 타입을 사용해야 할 때 유용하다.

-요약-

  • 타입과 인터페이스의 차이점과 비슷한 점을 이해해야 한다.
  • 타입과 인터페이스 둘다 표현가능하다면, 기존 코드와 일관되게 표현
  • 한 타입을 type과 interface 두 가지 문법을 사용해서 작성하는 방법을 터득해야 한다.
  • 프로젝트에서 어떤 문법을 사용할지 결정할 때 한가지 일관된 스타일을 확립하고, 보강이 필요한지 고려해야 한다.

0개의 댓글

관련 채용 정보