typescript 제네릭 (2)

BirdsOnTree·2022년 12월 20일
0

typescript

목록 보기
2/3
post-thumbnail

제네릭이란?

재사용성 높은 컴포넌트를 만들 때 사용되며, 한가지 타입보다 여러 타입에서 동작하는 컴포넌트를 생성하는데 사용한다.

제네릭 안쓰고 여러 타입을 받는 법 / 왜 제네릭을 써야하는가

제네릭을 사용하지 않고 any 타입을 사용하면 여러 타입을 넣을 수 있다. 하지만 any는 함수의 인자로 어떤 타입이 들어갔으며, 어떤 타입을 반환해야 하는지 알 수 없다.
any의 경우 타입체크를 하지 않는데, 이렇게 되면 관련 메소드가 힌트로 나오지 않고 타입 스크립트의 가장 좋은 장점인 컴파일단에서 버그를 걸러주지 않는다.

이를 위해 any를 사용하지 않고 제네릭을 사용함으로 아래의 문제를 커버할 수 있다.

function logText(text: any): any {
  console.log(text);
  return text;
}
logText(10);
logText(true);
logText("hi");

사용법

제네릭은 함수의 파라미터를 넣는 것과 같이 사용한다.

// 1. 어떤 타입을 받을 건지 먼저 정의 (logText<T>)
// 2. params 타입으로 정의 (text: T)
function logText<T>(text: T): T {
  console.log(text);
  return text;
}
// 3. 함수를 호출할때 타입 정의
const str = logText<string>("a");
str.split(""); // string으로 정의했기때문에 split 가능

logText<boolean>(true); // type: boolean
logText<string>("hi");
logText<number>(10);

위 코드는 text라는 파라미터에 값을 넘겨 text를 리턴한다. text에 어떤 값을 넣더라도 들어간 값에 대한 타입을 반환한다.

logText<number>(10);

logText 함수에는 넘기고자 하는 인자가 들어가고 그 인자에 대한 타입을 지정하면서 호출한다.

function logText<number>(text: number): number {
  console.log(text);
  return text; // 10
}
logText<number>(10);

제네릭으로 넣어준 T는 인자로 받은 number 타입을 받아 number 타입으로 바뀌게 되고,
동일하게 logText함수에 다른 타입을 지정하면 제네릭은 다르게 지정한 타입으로 바뀌게 된다.

제네릭 타입 가드 / 타입 제한

위 함수를 확장하여 만약 console.log(text.length)를 받는 다면 어떻게 할 수 있을까?

function logText<number>(text: number): number {
  console.log(text.length); // Property 'length' does not exist on type 'T'.ts(2339)
  return text;
}
logText<string>("dd");

text에 .length 메소드가 있다는 단서가 없기에 ts에서
Property 'length' does not exist on type 'T'.ts(2339)
같은 에러가 뜨게 된다.

string에 length 메소드가 있으나 ts 입장에서는 number, boolean을 넘기면 length 메소드가 없기 때문에 허용하면 안되는 상황인 것이다.

타입 가드를 이용해 특정 타입만 핸들링 할 수 있다.

function logText<T>(text: T): T {
  if (typeof text === "string") {
    console.log(text.length);
  }
  //string일 때만 length를 사용하도록 하는 것이죠.
  return text;
}

인터페이스와 extends

아래와 같이 length에 대한 메소드를 인터페이스로 지정하고 제네릭에 인터페이스를 extends 시킨다.
그에 따라 강제로 length 함수가 들어가게 되고 length 메소드를 실행할 수 있다.
만약 number를 넣는다면 number에는 length 메소드가 없기 때문에 LengthType 인터페이스에서 걸러지게 된다.

interface LengthType {
  length: number;
}

// 제네릭으로 받은 타입 T는 lengthType의 하위 타입이다. 즉, length: number는 무조건 포함됨
function logTextLength2<T extends LengthType>(text: T): T {
  text.length;
  return text;
}
logTextLength2("dd");
logTextLength2({ length: 3, q: 22 });
logTextLength2(1); 
// Argument of type 'number' is not assignable to parameter of type 'LengthType'.

인터페이스와 + 제네릭

아래의 두 코드는 같은 의미이다.

function logText<T>(text: T): T {
  return text;
}
// #1
let str: <T>(text: T) => T = logText;
// #2
let str: { <T>(text: T): T } = logText;

이와 같이 작성 가능

interface GenericLogTextFn {
  <T>(text: T): T;
}
function logText<T>(text: T): T {
  return text;
}
let myString: GenericLogTextFn = logText; // Okay
myString(true);
myString(11);
myString("hi");

인터페이스에 인자 타입을 강조 변경

interface GenericLogTextFn<T> {
  (text: T): T;
}
function logText<T>(text: T): T {
  return text;
}
let myString: GenericLogTextFn<string> = logText;
myString("hi"); // ok
myString(11); // error

제네릭이 가장 많이 쓰이는 부분

서버와 통신을 하는 api를 호출할때 제네릭을 가장 효율적으로 사용한다.
서버로부터 오는 res 값의 규칙에 제네릭을 쓴다. 프로미스는 제네릭 타입으로 정의된다.
: Promise<string[]>

function fetchItems(): Promise<string[]> {
  let items: string[] = ["a", "b", "c"];
  return new Promise(res => res(items));
}

async/await

interface Employee {
  id: number;
  employee_name: string;
  employee_salary: number;
  employee_age: number;
  profile_image: string;
}

const fetchEmployees = async (): Promise<Array<Employee> | string> => {
  const api = "http://dummy.restapiexample.com/api/v1/employees";
  try {
    const response = await fetch(api);
    const { data } = await response.json();
    return data;
  } catch (error) {
    if (error) {
      return error.message;
    }
  }
};

const fetchEmployee = async (
  url: string,
  id: number
): Promise<Record<string, string>> => {
  const response = await fetch(`${url}/${id}`);
  const { data } = await response.json();
  return data;
};

출처:
[기억보다 기록을]
https://kyounghwan01.github.io/blog/TS/fundamentals/generic/#async-await

0개의 댓글