toss/slash의 objectKeys() 분석

jh·2024년 6월 22일

참고자료
@toss/slash

Object.keys()의 문제

많이 알겠지만, Object.keys()는 인자로 넣은 객체의 타입을 보장해주지 않는다
무조건 Array<string> 타입이 리턴된다

[번역] 왜 타입스크립트는 Object.keys의 타입을 적절하게 추론하지 못할까요?
Jisu Yuk

이는 의도된 동작이라고 한다...
하지만 이후의 과정을 진행하기 위해서는 string이 아닌 정확한 타입이 필요하기 때문에 타입 단언 등의 과정이 필요하다

export const typography = Object.keys(typography).reduce((acc, key) => {
 	//typography[key] 타입 에러 발생
  const typoVariant = key as keyof typeof typography
  	//key의 타입은 string이기 때문에, 이후 과정을 위해 타입 단언이 필요할 수도 있다
  	typography[typoVariant]
  return acc
}, {} as Typography)

또한 동일한 값이지만 타입 단언을 위해 변수를 하나 더 추가해줘야 하는 귀찮음도 존재한다(이름짓기 귀찮음..)
그래서 이 과정을 좀 편하게 하는 방법이 있지 않을까 싶어 찾아봤는데, toss/slash 라이브러리에 해당 함수들이 있어서 한번 분석?해봤다

objectKeys()

type ObjectKeys<T extends Record<PropertyKey, unknown>> =
  `${Exclude<keyof T, symbol>}`

제일 처음 ObjectKeys 타입부터 보면, T라는 객체에서, symbol이라는 key값만 제외한 유니온 타입이다

  • PropertyKey 라는 유틸타입은 string | number | symbol 이다

  • 객체의 key값으로 symbol 이라는 데이터형이 올 수 있다

  • 하지만 Object.keys() 는 문자열 key 만 반환한다

  • 그렇기 때문에 symbol은 제외하는 것이 더 정확한 방법일 것이다

  • 아니면 아예 T의 key를 string | number 로만 하는것은?
    물론 symbol key 를 잘 쓰지는 않지만, 어떤 객체던 올 수 있게 만들어 놓는게 확장성 측면에서 더 좋을 것 같다

그러면 이제 이 타입을 가지고 함수를 만들어 보면

export function objectKeys<T extends Record<PropertyKey, unknown>>(
  obj: T,
): Array<ObjectKeys<T>> {
  return Object.keys(obj) as Array<ObjectKeys<T>>
}

const obj1 = { a: "1", b: 2,c: 3 }

const keys = objectKeys(obj1)//keys : (a | b | c)[]

함수의 동작은 크게 하는 건 없다(Object.keys() 뿐)
하지만 위에서 정의했던 ObjectKeys 라는 타입을 이용하여 리턴값을 단언했기 때문에, keys 는 정확한 타입을 가질 수 있게 된다

ObjectEntries()

그러면 비슷하게 Object.entries와 관련된 함수도 만들어 볼 수 있다

일단 리턴값을 생각해보면

  • 이차원 배열에,
  • 각 원소의 배열 길이가 2로 고정
  • 각 자리의 타입들은 고정이 되어있는 형태일 것이다
[[key,value],[key,value],[key,value]]

마침 TS에서는 튜플 타입을 제공하기 때문에, 이를 이용하면 더 정확하게 타입을 지정해줄 수 있다

type ObjectEntries<T extends Record<PropertyKey, unknown>> = Array<[ObjectKeys<T>,T[ObjectKeys<T>]]>

export function objectEntries<T extends Record<PropertyKey, unknown>>(
  obj: T,
): Array<[ObjectKeys<T>, T[ObjectKeys<T>]]> {
  return Object.entries(obj) as Array<[ObjectKeys<T>, T[ObjectKeys<T>]]>
}

const values = objectValues(obj1)//(["a" | "b" | "c", string | number][])

어차피 key의 타입은 위에서 구해놨기 때문에, 해당 key에 대한 value의 타입만 추가해주면 된다T[ObjectKeys<T>]

0개의 댓글