# 제너릭 타입

### 핵심 포인트

* 상수 타입 매개변수 **const T -** TS v5.0에 추가됨
  * 타입을 좁힐 때 사용 (ex: string을 유니온 리터럴로 추론되게 할 때)
* 타입 매개변수와 제약(extends)을 동일하게 생각하지 마라.
  * 타입 매개변수는 제약에 대입할 수 있는 모든 타입을 의미&#x20;
  * 타입 매개변수는 제약보다 더 넓은 타입, (제약이 더 좁은 타입이라 생각)
* 강박적으로 제너릭을 쓸 필요는 없다. 특히, 원시값 타입만 사용한다면 대부분 제약을 걸지 않아도 된다.

## 제너릭

> 타입간에 중복을 제거하기 위해 제너릭을 사용

```typescript
interface Person<N, A> {
  type: 'human',
  race: 'yellow',
  name: N,
  age: A,
}

interface Zero extends Person<'zero', 28> {}
interface Nero extends Person<'nero', 32> {}

// Array 타입도 제너릭
// 제너릭이 없었다면 스트링배열, 넘버배열을 따로 타입을 선언해줬어야 됬을것
interface Array<T> {
  [key: number]: T,
  length: number
  //...
}
```

* 표기는 `<>` 로 하며 인터페이스 이름 바로 뒤에 위치
* `<>` 안에 타입 매개변수를 넣어서 사용
* 제너릭 타입으로 타입간 중복을 줄이고 재사용성을 높힘

### 제너릭 표기 위치

> 함수에서 함수 선언문이냐 표현식이냐에 따라 제너릭 표기 위치가 달라짐
>
> **함수는 제너릭표기가 인자 앞에 위치**한다고 생각

```typescript
const personFactoryE = <N, A>(name: N, age: A) => {
  ...
}

function personalFactoryD<N,A>(name: N, age: A) {
  ...
}
```

* **interface** 이름<타입 매개변수들> {...}
* **type** 이름<타입 매개변수들> = {...}
* **class** 이름<타입 매개변수들> {...}
* **function** 이름<타입 매개변수들>(...) {...}
* **const** 함수이름 = <타입 매개변수들>(...) ⇒ {...}

### 제너릭 기본값

> 타입 매개변수에 기본값을 사용할 수 있다.

```typescript
interface Person<N = string, A = number> {
  type: 'human',
  race: 'yellow',
  Name: N,
  age: A
}

type Person1 = Person; // Person<string, number>
type Person2 = Person<number>; // Person<number, number>
type Person3 = Person<number, boolean>; // Person<number, boolean>
```

### 타입 추론

> 타입스크립트는 제너릭에 직접 타입을 넣지 않아도 추론을 통해 타입을 알아 낼 수 있다.

```typescript
interface Person<N, A> {
  type: 'human',
  race: 'yellow',
  Name: N,
  age: A
}

const personFactoryE = <N, A = unknown>(name: N, age: A): Person<N,A> => ({
  type: 'human',
  race: 'yellow',
  name,
  age,
})

const zero = personFactoryE('zero', 28); // const zero: Person<string, number>
```

* 이처럼 **타입스크립트가 추론을 통해 타입을 알아낼 수 있는 경우는 직접 `<>` 타입을 넣지 않아도 된다.**
  * **실제로도 직접 넣지 않는 경우가 더많음**
* 타입스크립트 5.0 버전에서 **상수 타입 매개변수 `(const T)`**&#xAC00; 추가됨

#### 상수 타입 매개변수

```typescript
function values<T>(initial: T[]) {
  return {
    hasValue(value:T) { return initial.includes(value) }
  }
}

const savedValues = values(['a','b','c']); // T는 string[]로 추론됨
savedValues.hasValue('x'); // T가 string으로 추론되기에 'x'를 넣어도 타입 오류가 나지 않음
```

* ❓T를 `string` 대신 `'a' | 'b' | 'c'` 같은 유니온으로 추론되게 하려면?
  * 타입 매개변수 앞에 **const** 수식어를 추가하면 타입 매개변수 T를 추론할 때 as const를 붙인 값으로 추론됨

```typescript
// TS v4.9 
function values<T>(initial: readonly T[]) { //readonly 수식어로 정확한 타입을 추론하게끔 설정
  //...
}

const savedValues = values(['a','b','c'] as const); // as const 접미사로 tuple로 만듬
savedValues.hasValue('x'); // T가 'a' | 'b' | 'c' 이므로 에러 발생

// TS v5.0
function values<const T>(initial: T[]) { // 타입 매개변수 앞에 const 수식어 사용
  //...
}

const savedValues = values(['a', 'b', 'c']);
savedValues.hasValue('x'); // 타입 에러 발생
```

### 제너릭에 제약 걸기

> 타입 매개변수에는 제약(constraint)를 사용 가능
>
> **`extends`** 문법으로 타입 매개변수의 제약을 표시

* <mark style="color:red;">**타입의 상속을 의미하던 extends 와는 사용법이 다르므로 구분!!**</mark>
* <mark style="color:purple;">**제약이 걸리면 제약에 어긋나는 타입은 입력할 수 없지만 제약보다 더 구체적인 타입은 입력 가능**</mark>
  * 이러한 점에서 제약은 기본값과는 다르다.

```typescript
interface Example<A extends number, B = string> {
  a: A,
  b: B
}
type Usecase1 = Example<string, boolean>; // 타입 에러
type Usecase2 = Example<1, boolean> // number 보다 더 구체적인 타입 입력 가능
type Usecase3 = Example<number>

// 하나의 타입 매개변수가 다른 타입 매개변수의 제약이 될 수 도 있다.
interface Example<A, B extends A> {
 //...
}
```

{% hint style="success" %}
**‼️ 자주 쓰이는 제약들**&#x20;

```typescript
<T extends object> // 모든 객체
<T extends any[]> // 모든 배열
<T extends (...args: any) => any> // 모든 함수
<T extends abstract new (...args: any) => any> // 생성자 타입
<T extends keyof any> // string | number | symbol
```

{% endhint %}

### 제너릭 제약을 사용할 때 흔히 하는 실수

* ❌ 타입 매개변수와 제약을 동일하게 생각하는 것&#x20;
* 먼저 타입 매개변수가 제약에 대입할 수 있는 타입인지를 따져보아야 한다.
* 강박적으로 제너릭을 쓸 필요는 없다. 특히, 원시값 타입만 사용한다면 대부분 제약을 걸지 않아도 된다.

```typescript
interface VO {
  value: any;
}
// ☠️ 타입 매개변수와 제약을 동일하게 생각해서 발생하는 실수
const returnVO = <T extends VO>(): T => {
  return { value: 'test' }; // Type Error
}

// 제너릭을 제거하면됨
const returnVO = (): VO => {
  return { value: 'test' };
}
```

* <mark style="color:purple;">**`T`**</mark><mark style="color:purple;">**&#x20;**</mark><mark style="color:purple;">**는 정확히**</mark><mark style="color:purple;">**&#x20;**</mark><mark style="color:purple;">**`VO`**</mark><mark style="color:purple;">**가 아니라**</mark><mark style="color:purple;">**&#x20;**</mark><mark style="color:purple;">**`VO`**</mark><mark style="color:purple;">**에 대입할 수 있는 모든 타입을 의미**</mark>
  * 따라서 `{ value: string, another: string }`도 T가 될 수 있다.
  * 이러면 `{ value: string }` 은 T가 아니다. 따라서 에러가 발생함.
* 인자값의 타입은 열려있고, 반환값의 타입은 닫혀있다. (공변성, 반공변성 참고)

```typescript
// ☠️ 타입 매개변수 T 에 boolean 제약이 걸려있고, 함수의 매개변수도 T 타입
function onlyBoolean<T extends boolean>(arg: T = false): T { // 인자 타입에서 에러 발생
  return arg;
}

// onlyBoolean을 유효하게 만들고 싶다면 간단하다. 제너릭을 쓰지 않으면 된다.
function onlyBoolean(arg: boolean = true): boolean {
  return arg;
}
```

* **`never`** 타입 때문, never는 모든 타입에 대입할 수 있으므로 **`never extends boolean`**&#xC740; 참이다.
* 따라서 **T가 never 일 수 있으므로 false를 기본값으로 넣는 것이 불가능**한 것


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://taewoongs-organization.gitbook.io/jtwjs-dev-wiki/dev_note/typescript/undefined/undefined-2.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
