首页 > 解决方案 > TypeScript 按属性分组,具有准确的键值关联

问题描述

我想要一个按功能分组,其中组类型被精确建模。例如,在下面很明显tag.value.format()应该键入 check 因为我们正在release小组中工作。

const tags: ParsedTag[] = [
  { type: 'release', value: Semver.parse('1.2.3')! },
  { type: 'unknown', value: 'foobar' },  
]

const tagGroups = groupByProp(tags, 'type')

// Goal: .format type checks
console.log(tagGroups.release.map(tag => tag.value.format()))

我尝试构建这样的功能可以在这个 TS Playground 实例中找到。

标签: typescript

解决方案


我找到了解决方案。完整的代码是:

type IndexableKeyTypes = string | number | symbol

type Indexable<T = unknown> = Record<string | number, T>

type JustIndexableTypes<T> = T extends IndexableKeyTypes ? T : never

type KeysMatching<Rec, Keys> = NonNullable<
  {
    [RecKey in keyof Rec]: Rec[RecKey] extends Keys ? RecKey : never
  }[keyof Rec]
>

type GroupBy<T extends Indexable, K extends IndexableKeys<T>> = {
  [KV in JustIndexableTypes<T[K]>]: Array<T extends Record<K, KV> ? T : never>
}

type IndexableKeys<Rec> = KeysMatching<Rec, IndexableKeyTypes>

export function groupByProp<Obj extends Indexable, KeyName extends IndexableKeys<Obj>>(
  xs: Obj[],
  keyName: KeyName
): GroupBy<Obj, KeyName> {
  type KeyValue = JustIndexableTypes<Obj[KeyName]>
  const seed = {} as GroupBy<Obj, KeyName>

  return xs.reduce((groupings, x) => {
    const groupName = x[keyName] as KeyValue

    if (groupings[groupName] === undefined) {
      groupings[groupName] = []
    }

    groupings[groupName].push(
      x as Obj extends Record<KeyName, KeyValue> ? Obj : never
    )

    return groupings
  }, seed)
}

//
// TEST
//

import * as Semver from 'semver'

type ParsedTag =
  | { type: 'unknown'; value: string }
  | { type: 'release'; value: Semver.SemVer }

const tags: ParsedTag[] = [
  { type: 'release', value: Semver.parse('1.2.3')! },
  { type: 'unknown', value: 'foobar' },  
]

const tagGroups = groupByProp(tags, 'type')

 // Goal: .format type checks
console.log(tagGroups.release.map(tag => tag.value.format()))

可以在此处找到 TS Playground 实例的链接。

起初我遇到困难的部分(导致原始问题)是:

type GroupBy<T extends Indexable, K extends IndexableKeys<T>> = {
  [KV in JustIndexableTypes<T[K]>]: Array<T extends Record<K, KV> ? T : never>
}

我意识到通过迭代键值类型,我可以在T.

该解决方案依赖于它们是工会中的歧视性财产,这在我看来是合理和直观的。


推荐阅读