首页 > 解决方案 > TypeScript:在对象之间转换元组的类型安全方式?

问题描述

在最近的一个项目中,我有很多基本上命名为元组的类,我经常发现自己需要在这些对象和元组之间进行转换。这意味着,我基本上有很多这样的功能:

const tupleToFoo = ([a, b, c]) => new Foo(a, b, c)
const fooToTuple = (foo) => [foo.a, foo.b, foo.c]

我可能应该把它留在那里,但我忍不住想知道是否有更通用的方法来编写这段代码。这是我想出的:

type AnyConstructor<T> = { new (...args: any[]): T }

type KeysOf<T> = ReadonlyArray<keyof T>
type Lookup<T, K> = K extends keyof T ? T[K] : never
type UnionFromInterface<T, K extends KeysOf<T>> = T[K[number]]
type TupleFromInterface<T, K extends KeysOf<T>> = {
  [I in keyof K]: Lookup<T, K[I]>
}

function construct<T, K extends ReadonlyArray<keyof T>>(
  C: AnyConstructor<T>
): (t: UnionFromInterface<T, K>[]) => T {
  return t => new C(...t)
}

function deconstruct<T, K extends ReadonlyArray<keyof T>>(
  D: K
): (o: T) => UnionFromInterface<T, K>[] {
  return o => D.map(k => o[k])
}

function tupleToUnions<T, K extends ReadonlyArray<keyof T>>(
  t: TupleFromInterface<T, K>
): UnionFromInterface<T, K>[] {
  return t.map(i => i as UnionFromInterface<T, K>)
}

// This is particularly bad... :(
function unionsToTuple<T, K extends ReadonlyArray<keyof T>>(
  u: UnionFromInterface<T, K>[]
): TupleFromInterface<T, K> {
  return (u as unknown) as TupleFromInterface<T, K>
}

function instanceToTuple<T, K extends ReadonlyArray<keyof T>>(
  T: (o: T) => UnionFromInterface<T, K>[]
): (o: T) => TupleFromInterface<T, K> {
  return o => unionsToTuple(T(o))
}

function tupleToInstance<T, K extends ReadonlyArray<keyof T>>(
  O: (t: UnionFromInterface<T, K>[]) => T
): (t: TupleFromInterface<T, K>) => T {
  return t => O(tupleToUnions(t))
}

可以按如下方式使用:

class Person {
  static Keys = ['name', 'hobbies'] as const

  name: string
  hobbies?: string[]

  constructor(name: string, hobbies?: string[]) {
    this.name = name
    this.hobbies = hobbies
  }
}

const tupleToPerson = tupleToInstance(
  construct<Person, typeof Person.Keys>(Person)
)
const personToTuple = instanceToTuple(
  deconstruct<Person, typeof Person.Keys>(Person.Keys)
)

const p = new Person('John Doe', ['yodeling'])

const t = personToTuple(p)
const q = tupleToPerson(t)

console.log(p, t, q)

虽然这“有效”,但问题在于它根本不是类型安全的。感觉应该有一种类型安全的方式来执行此操作,因为所有必要的信息在编译时都可用。因此,我很好奇是否有人知道一种类型安全的方法来实现这样的东西?

标签: typescriptgenericstype-safety

解决方案


推荐阅读