본문 바로가기

FrontEnd

The constrained identity function : Record Type의 키타입 정의 생략.

반응형

typescript

TLDR : CIF와 레코드 타입의 키타입 정의를 피하자.

Typescript를 쓰다보면 똑같은 타입을 반복해서 작성하는 거지같은 짓을 많이 본다.

잘못된 타입을 여러곳에서 여러 사람들이 다른 이름으로 쓰는 것도 많이 보게된다.

타입 관리도 비용이다.

How to write a Constrained Identity Function (CIF) in TypeScript (kentcdodds.com)

 

How to write a Constrained Identity Function (CIF) in TypeScript

A handy advanced TypeScript pattern to increase your productivity.

kentcdodds.com

해당 게시물의 요약본이다.

문제점

const operations = {
  '+': (left: number, right: number): number => left + right,
  '-': (left: number, right: number): number => left - right,
  '*': (left: number, right: number): number => left * right,
  '/': (left: number, right: number): number => left / right,
}

type CalculatorProps = {
  left: number
  operator: keyof typeof operations
  right: number
}

function Calculator({left, operator, right}: CalculatorProps) {
  const result = operations[operator](left, right)
  return (
    <div>
      <code>
        {left} {operator} {right} = <output>{result}</output>
      </code>
    </div>
  )
}

const examples = (
  <>
    <Calculator left={1} operator="+" right={2} />
    <Calculator left={1} operator="-" right={2} />
    <Calculator left={1} operator="*" right={2} />
    <Calculator left={1} operator="/" right={2} />
  </>
)

해당 컴포넌트를 타이핑한다고 생각하자.

type OperationFn = (left: number, right: number) => number
type Operator = '+' | '-' | '/' | '*'
const operations: Record<Operator, OperationFn> = {
  '+': (left, right) => left + right,
  '-': (left, right) => left - right,
  '*': (left, right) => left * right,
  '/': (left, right) => left / right,
}

type CalculatorProps = {
  left: number
  operator: keyof typeof operations
  right: number
}

operations 추가 시 Operator 타입을 추가한다. 귀찮다.

목표

1. "keyof typeof Operator"는 union(product) 타입

2. 각 프로퍼티의 타입은 동일. (Record<A,B>)

문제점 2

문제점1의 예제는 목표 2 달성

아래는 아님... 

(타입스크립트는 가능한 한 넓게 타입을 추론하기 때문)

type OperationFn = (left: number, right: number) => number
const operations: Record<string, OperationFn> = {
  '+': (left, right) => left + right,
  '-': (left, right) => left - right,
  '*': (left, right) => left * right,
  '/': (left, right) => left / right,
}




type a = keyof typeof operations; // a : string

 

해결방안

타입스크립트에게 키 타입 추론을 좁히고, 벨류 타입 범위를 넓히도록 하자.

CIF(씨 아이 에프)

type Value = number
const createNumbers = <ObjectType extends Record<string, Value>>(
  obj: ObjectType,
) => obj

type OperationFn = (left: number, right: number) => number
const createOperations = <OperationsType extends Record<string, OperationFn>>(
  operations: OperationsType,
) => operations

type a = keyof typeof operations; // a : '+' | '-' | '/' | '*'

조금 더 일반화한 방법

const constrain =
  <Given extends unknown>() =>
  <Inferred extends Given>(item: Inferred) =>
    item

const numbers = constrain<Record<string, number>>()({one: 1 /* etc. */})

// or

const createNumbers = constrain<Record<string, number>>()
const numbers = createNumbers({one: 1 /* etc. */})

결론 : 타입스크립트의 진화는 계속될 필요가 있다.

반응형