타입스크립트4: Polymorphism, Generics Recap, Conclusions

윤뿔소·2023년 2월 9일
0

TS

목록 보기
2/5

Polymorphism(다형성)

인자들과 반환값에 대하여 형태(타입)에 따라 그에 상응하는 형태(타입)를 가질 수 있다. 제네릭!

타입스크립트에는 2가지 타입이 있다.

  1. Concrete type: number, boolean, void 등 지금까지 배운 타입
  2. Generic type: 타입의 placeholder 같은 것.

즉, 제네릭 타입을 통해 다양한 타입 즉, 다형성을 띨 수 있다.

예시

T라는 제네릭 타입을 선언해 각 배열의 요소 타입에 맞춰서 나오는 모습이다.

type SuperPrint = {
  (arr: T[]): T;
};

const superPrint: SuperPrint = (arr) => {
  return arr[0];
};

// 유추해서 시그니처를 보여줌
const a = superPrint([1, 2, 3]);
// const superPrint: <number>(arr: number[]) => number;
const b = superPrint([true, false, true]);
// const superPrint: <boolean>(arr: boolean[]) => boolean;
const c = superPrint(["a", "b"]);
const d = superPrint([1, 2, "a", "b", true]);
// const d: string | number | boolean

아래 함수 선언문을 보면 다양한 타입을 받고 있지 않는가? 전 시간에 했던 Call Signature로 다 맞춰줄 수 없는 노릇이다.
여기서 제네릭 타입 T를 입력해 a(arr: number[]), d(arr:(number|string|boolean)[])와 같은 효력을 가지게 된다. 물론 저렇게 모든 조합을 적어줘도 되지만 별로 바람직하진 않다.d의 호출 시그니처

즉, 제네릭은 타입이 어떻게 나오는지 추론한다는 뜻이다!

제네릭 사용법

우선 제네릭을 쓰는 이유는 any처럼 처리하기 편하면서도 한 함수 안에서 타입을 담아 전달할 수 있기 때문에 강력한 타입 체크도 겸할 수 있기에 좋다.

제네릭 타입의 변수 이름으로 보통 JS 변수처럼 이름을 붙여줘도 되지만 T(타입), V(밸류)를 많이 쓴다. 앞에 브라켓을 넣어서 변수 선언한다.

// 타입
type SuperPrint = {
  <T>(arr: T[]): T;
};
// 인터페이스: 브라켓 有
interface SuperPrint {
  <T>(arr: T[]): T;
}
interface SuperPrint {
  <TypePlaceholder>(arr: TypePlaceholder[]): TypePlaceholder;
}
// 함수에 바로 적용
const superPrint<T>(arr: T[]): T = (arr) => {
  return arr[0];
};

Generics Recap

타입의 다형성을 보며 제네릭을 배웠다. 제네릭이 정확히 뭔지 배워보자

제네릭은 C#이나 Java와 같은 정적 언어에서 재사용 가능한 컴포넌트를 만들기 위해 사용하는 기법. 단일 타입이 아닌 다양한 타입에서 작동할 수 있는 컴포넌트를 생성 가능.
또한 ⭐️제네릭은 선언 시점이 아니라 (타입)생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법.

즉, 타입스크립트에서 제네릭을 통해 any와 달리 타입을 추정하고 인터페이스, 함수 등의 재사용성을 높일 수 있다.

기본적인 사용법은 위에서 봤으니 하나의 함수에서 제네릭을 여러개 써서 활용성을 더욱 높여보자!

interface SuperPrint {
  <T, M>(arr: T[], b: M): T;
}
const superPrint: SuperPrint = (arr) => {
  return arr[0];
};
const a = superPrint([1, 2, 3], "x");
const b = superPrint([1, 2, 3], [1]);

위의 함수로 T, M이라는 제네릭이 들어오고 첫번째 파라미터로 T(배열)가, 두번째 파라미터로 M이 들어온다.보면 알겠지만 2번째 파라미터의 타입도 추정해서 받아주는 모습

제네릭 꿀팁

제네릭은 대부분의 외부 라이브러리들이 쓴다. 위의 이점이 있으니까 말이다. 그렇다면 사용하는 우리는? 제네릭을 알 수가 없는데? 하지만 여기서도 우리만의 제네릭을 갖다가 붙이면 제네릭이 알아서 타입 추정을 해주고 받아오니 우리는 선언했던 제네릭을 쓰면 된다는 것이다!

즉! 외부 라이브러리를 사용할 때 타입을 모른다면?! 제네릭을 무적권 권장한다! 타입스크립트가 타입을 추정하도록 나두는 것이 제일 베스트!

  • call signature를 잘 안쓴다 사실 ㅋㅋ

Conclusions

이렇게 타입들을 확장하며 타입을 지정할 수 있다.

// 제네릭 E로 타입 지정이 된다.
type Player<E> = {
  name: string;
  extraInfo: E;
};

type MePlayer = Player<MeExtra>;

type MeExtra = { age: number };
// MePlayer를 쓴 함수들은 extraInfo가 age:num으로 오는 것들이다.
const player: MePlayer = {
  name: "joseph",
  extraInfo: {
    age: 23,
  },
};
// 이렇게 직접 지정 가능
const player2: Player<null> = {
  name: "Yee",
  extraInfo: null,
};

이런 식으로 활용이 무궁무진하다. Player를 가져와 직접 지정할 수도 있고,Player를 활용한 MePlayer를 가져와 extraInfo가 객체로 이루어진 애들한테도 사용할 수 있다.

예시

대부분 라이브러리와 개발자들은 제네릭을 쓰며 상속을 받아서 편하게 쓰는 식으로 개발해왔다.

useState

제네릭이 쓰이는 가장 대표적인 것이 리액트 훅인 useState다. 리액트 + TS를 쓸 때 많이 쓰던 방식이 있다.

// 에러 타입지정 필요
const [] = useState();
// 브라켓으로 감싼 제네릭을 써서 타입 지정
const [] = useState<number>();

이런 식으로 브라켓을 써서 타입을 지정했지 아니한가? 저렇게 Hook들에게도 제네릭이 있어 저렇게만 써줘도 타입지정이 가능했던 것이다! 유레카!@

과제 중 수상한 점

  • 현재까지 배운 것을 토대로, 두 함수에 대한 구현과 함께 호출 시그니처(call signatures) 를 작성해주세요
  • last(arr): 이 함수는 배열의 마지막 요소를 반환해야 합니다.
  • prepend(arr, item): 이 함수는 배열의 시작 부분에 item을 넣고 return해야 합니다.
interface SuperPrint {
    <T>(arr: T[]): T;
}
interface SuperPrintPlus {
    <T>(arr: T[], item: T): T[];
}

const last = (arr): SuperPrint => {
    const arrLen = arr.length - 1;
    return arr[arrLen];
};

const prepend = (arr, item): SuperPrintPlus => {
    return [item, ...arr];
};
// 이건 안됨..

console.log(last([1,2,3]))
console.log(prepend([1,2,3], 0))
// function last<T>(arr: T[]): T
function last<T>(arr: T[]): T {
  return arr[arr.length - 1];
}
// function prepend<T>(arr: T[], item: T): T[]
function prepend<T>(arr: T[], item: T): T[] {
  return [item, ...arr];
}

console.log(last([1,2,3]))
console.log(prepend([1,2,3], 0))

아래는 됨 무슨 차이지..? 위에는 파라미터가 any타입이라 안된다고 뜬다.. 똑같이 제네릭으로 적용했는데 왜 타입을 추정하지 못했을까?

profile
코뿔소처럼 저돌적으로

0개의 댓글