使用 TypeScript 强制对象中的元组是对象中另一个元组的子集?

问题描述 投票:0回答:1

我有一个函数接受带有查询参数的对象。下面是查询参数的简化示例。我想确保

sortBy
中的任何值都存在于
fields
中。我可以在运行时检查这一点,但我想知道是否可以使用 TypeScript 来做到这一点。

const validQueryArgs = {
  fields: ['a', 'b', 'c'] as const,
  sortBy: ['a'] as const
};

const invalidQueryArgs = {
  fields: ['a', 'b', 'c'] as const,
  sortBy: ['d'] as const // ts error here would be great :) 
};

我知道你可以按照这个思路做一些事情(Matt Pocock 有一个关于这个的 YouTube 短片):

function createQueryArgs<T>({sortBy, fields}: { sortBy: NoInfer<T>, fields: T[] }): {fields: T[], sortBy: T } { 
    return {
      fields,
      sortBy
    }
}

const someArgs = createQueryArgs({
    sortBy: "c", // error here :thumbs-up:
    fields: ["a", "b"] as const
})

有什么方法可以在不使用传递函数的情况下做到这一点吗?

我尝试走条件类型的道路,但很快就陷入了死胡同。

type CheckQueryArgs<T> = T extends { fields: infer Fields, sortBy: infer SortBy } ?
  SortBy extends Fields ?
  true :
  never :
  never

type CheckInvalid = CheckQueryArgs<typeof invalidQueryArgs> // never :thumbs-up:

type CheckValid = CheckQueryArgs<typeof validQueryArgs> // never :thumbs-down:

🔗 TypeScript 游乐场

谢谢!

typescript
1个回答
0
投票

A

type
无法描述关系中没有使用特定类型的关系。

这意味着虽然这有效:

type TestA<T extends string[]>{ items: T, item: T[number] }

您需要能够满足

T
才能使用该类型。所以你不能这样做:

const objA: TestA = { items: [1,2,3], item: 2 } // wont work

使用函数是提供推理点的一种方法,该推理点可以为

T
提供类型。


但是如果您想要一种类型来检查这些对象之一,您可以这样做。您的尝试:

type CheckQueryArgs<T> = T extends { fields: infer Fields, sortBy: infer SortBy } ?
  SortBy extends Fields ?
  true :
  never :
  never

失败,因为

Fields
被推断为
readonly ["a", "b", "c"]
并且
SortBy
被推断为
readonly ["c"]
。这两种类型都不会扩展另一种类型,因为它们是具有不同成员数量的元组,因此它们不能相互分配。

您需要通过

number
对这些类型进行索引,以获取该元组中可能的值作为联合。然后一个可以扩展另一个。


将所有这些放在一起,你可以做这样的事情:

type CheckQueryArgs<T extends QueryArgs> =
  T['sortBy'][number] extends T['fields'][number]
    ? T
    : never

type CheckInvalid = CheckQueryArgs<{ fields: ['a', 'b'], sortBy: ['c'] }>
// never

type CheckValid = CheckQueryArgs<{ fields: ['a', 'b'], sortBy: ['a'] }>
// { fields: ['a', 'b'], sortBy: ['a'] }

看游乐场

© www.soinside.com 2019 - 2024. All rights reserved.