본문 바로가기

카테고리 없음

Typescript 챌린지#1) never와 infer 키워드는 무엇인가?

최근에 OSSA에 같이 합류하게 되어 Githru팀으로 챌린지를 진행하고 있다. 챌린지에서는 타입스크립트와 d3에 대해 공부하는 시간을 갖고 마스터에서는 실제로 오픈소스 플랫폼을 이용해 협업하여 실 서비스를 배포한다고 한다.

팀에서 추천받은 타입스크립트 튜토리얼 중에 타입스크립트 챌린지라는 것이 있는데 요즘 이것을 하고 있다. 그런데 타입스크립트에 대해 정말 모르는 것이 많아서 블로그에 챌린지를 하면서 궁금했던 것, 왜 이건 이렇게 되는 건지 알게 된 것을 기록해두려고 한다.

오늘은 never와 infer 키워드에 대해 정리해보려고 한다.

 

never 타입에 대해

간단하게 설명하자면 값의 공집합이라고 한다. 집합에 어떤 값도 없기 때문에 never 타입은 any 타입의 값을 포함해 어떤 값도 가질 수 없다.

그렇다면 왜 never 타입이 필요한 걸까?

숫자 체계에서의 0도, 문자 체계에서의 불가능도 타입으로 설명할 수 있어야 타입을 체크할 수 있다. 그래서 never 타입이 탄생했고 타입스크립트에서는 불가능을 다음과 같이 나타내고 있다.

  • 값을 포함할 수 없는 빈 타입
    • 제네릭과 함수에서 허용되지 않는 매개변수
    • 호환되지 않는 타입들의 교차 타입
    • 빈 합집합(무의 합집합)
  • 실행이 끝날 때 호출자에게 제어를 반환하지 않는 함수의 반환 타입
    • Node의 process.exit
    • void는 호출자에게 함수가 유용한 것을 반환하지 않는다는 것이므로 혼동하지 않도록 한다
  • 절대로 도달할 수 없을 else 분기의 조건 타입
  • 거부된 Promise에서 처리된 값의 타입

유니언/교차 타입과 never의 동작

type Res = never | string // string
type Res = never & string // never

never 타입은 어떻게 쓸까?

허용할 수 없는 함수 매개변수에 제한을 가한다.

switch, if-else 문의 모든 상황을 보장한다.

function fn(input: never) {}

// 오직 `never` 만 받는다.
declare let myNever: never
fn(myNever) // ✅

// 아무 값이나 전달하거나 아무 값도 전달하지 않으면 타입 에러 발생
fn() // ❌ 인자 'input'에 아무 값도 주어지지 않음
fn(1) // ❌ 'number' 타입은 'never' 타입에 할당할 수 없음
fn('foo') // ❌ 'string' 타입은 'never' 타입에 할당할 수 없음

// `any`도 통과할 수 없다.
declare let myAny: any
fn(myAny) // ❌ 'any' 타입은 'never' 타입에 할당할 수 없음


/*
|
|
|
|
----> never로 고치면 다음과 같다
*/
function unknownColor(x: never): never {
    throw new Error("unknown color");
}


type Color = 'red' | 'green' | 'blue'

function getColorName(c: Color): string {
    switch(c) {
        case 'red':
            return 'is red';
        case 'green':
            return 'is green';
        default:
            return unknownColor(c); // 'string' 타입은 'never' 타입에 할당할 수 없음
    }
}

타이핑을 부분적으로 허용하지 않는다.

의도하지 않은 API 사용을 방지한다.

이론적으로 도달할 수 없는 분기를 표기한다. <-- 최근에 챌린지에서 쉬움이지만 가장 많이 발견하고 있는 사례이다.

type A = 'foo';
type B = A extends infer C ? (
    C extends 'foo' ? true : false// 이 표현식 내에서 'C'는 'A'를 나타낸다.
) : never // 이 분기는 도달할 수 없지만, 생략도 할 수 없다.

유니언 타입에서 멤버 필터링

매핑된 타입의 키 필터링

제어 흐름 분석의 좁은 타입

호환되지 않는 타입의 불가능한 교차 타입 표시

never 타입을 검사하려면

type IsNever<T> = T extends never ? true : false

type Res = IsNever<never> // never 🧐
// --------------------> 타입 스크립트는 조건부 타입에 대해 자동적으로 유니언 타입을 할당한다. nver은 빈 유니언 타입이다. 그러므로 할당이 발생하면 할당할 것이 없으므로 조건부 타입은 never로 평가된다.

type IsNever<T> = [T] extends [never] ? true : false;
type Res1 = IsNever<never> // 'true' ✅
type Res2 = IsNever<number> // 'false' ✅

 

출처 : https://ui.toast.com/weekly-pick/ko_20220323

 

타입스크립트의 Never 타입 완벽 가이드

타입스크립트의 never 타입은 다른 타입만큼 흔하게 사용되거나 피할 수 없는 것이 아니기 때문에 충분히 논의되고 있지 않다. 타입스크립트 초보자는 조건부 타입 같은 고급 타입을 처리하거나

ui.toast.com

 


 

infer 키워드에 대해

infer 키워드는 조건부 타입에서 사용하면 실제 분기의 비교할 유형에서 추론하는 방법을 제공할 수 있다.

type GetReturnType<T> = T extends (...args: never[]) => infer R ? R : never;

type Num = GetReturnType<() => number>; // type is number
type Str = GetReturnType<(x: string) => string>; // type is string
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>; // type is boolean[]

// ------------------------------------------------------------------------------------

type ReturnPromise<T> = T extends (...args: infer A) => infer R ? (...args: A) => Promise<R> : T;

type Promisify<T> = {
  [P in keyof T]: ReturnPromise<T[P]> // ref: Mapped Types
}

interface IItemService {
  id: string;
  getItem(): string;
  getItems(): string[];
}

class ItemService implements Promisify<IItemService> {
  id: string = '';

  getItem() { // type is Promise<string>
    return Promise.resolve('')
  }

  getItems() { // type is Promise<string[]>
    return Promise.resolve([''])
  }
}
  • ReturnPromise 타입은 제네릭 타입
  • A 타입으로 추론되는 여러 개의 파라미터를 가지며,
    리턴 타입이 R로 추론되는 함수 (...args: infer A) => infer RT 타입이 할당될 수
    있다면 (참일 경우, 타입이 추론 되었기 때문에 타입을 확정할 수 있다) (...args: A) => Promise<R>의 타입을 가지며
    그렇지 않을 경우 T 타입을 갖는다. (위의 코드에서 id가 프로미스로 랩핑되지 않고 그대로 string 타입을 갖는 이유이다)
     

출처 : https://ajdkfl6445.gitbook.io/study/typescript/condition-type-+-infer#condition-type 

 

조건부 타입(condition Type) + infer - 콥 노트

위 코드를 보면 any를 사용했는데, 이유는 타입스크립트가 타입 유추를 하지 않도록 타입 단언(type assertion) as와 any를 사용했다. 왜냐하면 id 타입이 조건부 타입으로 선택되는 것이 아니므로 함수

ajdkfl6445.gitbook.io