打字稿:具有不同参数的泛型数组

时间:2021-07-21 13:27:17

标签: typescript

考虑这样一个函数:

func([
   {object: object1, key: someKeyOfObject1},
   {object: object2, key: someKeyOfObject2}
])

它有一个数组。我想强制 key 字段保存 object 中保存的对象的键。 每个物体都可以有不同的形状。

为单个对象构造这样的类型很容易:

type Type<T> = { obj: T, key: keyof T }

但是,我不知道如何从中创建一个数组,其中每个元素都将被强制执行。 Type<any>[] 将删除所有类型。

2 个答案:

答案 0 :(得分:1)

在功能方面限制这一点很困难。我什至不认为有一种通用的方法可以做到这一点。

非泛型解决方案:函数重载

interface Item<TObject> {
  object: TObject
  key: keyof TObject
}

function func<T1, T2, T3, T4>(items: [Item<T1>, Item<T2>, Item<T3>, Item<T4>]): void
function func<T1, T2, T3>(items: [Item<T1>, Item<T2>, Item<T3>]): void
function func<T1, T2>(items: [Item<T1>, Item<T2>]): void
function func<T1>(items: Item<T1>[]): void {

}

func([
  { object: { a: '1' }, key: 'a' },
  { object: { b: '1' }, key: 'b' },
  { object: { c: '1' }, key: 'a' }, // not allowed
])

解决方案 2:在调用方强制类型 基本上,您依赖于效用函数。仍然有办法在这里出错并让编译器错过它(参见示例中的最后一项)

interface Item<TObject extends object> {
  object: TObject
  key: keyof TObject
}

function func(items: Item<any>[]) {

}

function createItem<T extends object>(object: T, key: keyof T): Item<T> {
  return {
    object,
    key
  }
}

func([
  createItem({ a: 1 }, 'a'),
  createItem({ b: 2 }, 'f'), // not allowed
  { object: { b: 2 }, key: 'f' }, // allowed
])

解决方案 3:使用通用 add 方法创建处理器对象

interface Item<TObject> {
  object: TObject
  key: keyof TObject
}

function createMyArrayProcessor() {
  const array: Item<any>[] = []

  return {
    add<T>(item: Item<T>) {
      array.push(item)

      return this
    },
    result() {
      // process the array here and return the result
    }
  }
}

const result = createMyArrayProcessor()
  .add({ object: { a: '1' }, key: 'a' })
  .add({ object: { b: '1' }, key: 'b' })
  .add({ object: { c: '1' }, key: 'a' }) // not allowed
  .result()

答案 1 :(得分:0)

无需额外功能即可实现,但类型开销很小:

type Entity<Obj, Key> = {
    object: Obj,
    key: Key
}

type IsValid<T extends Entity<any, any>[]> = 
    /**
     * Infer each element of the array
     */
    T[number] extends infer Elem 
    /**
     * Check if every element of the array extends Entity
     */
    ? Elem extends Entity<any, any> 
    /**
     * Check if keyof Elem['object'] extends `key` property
     * 1) if [key] property is one of object properties - return true
     * 2) if at least one element does not meet your requirements return false | true,
     * because some element are ok
     */
    ? keyof Elem['object'] extends Elem['key'] 
    ? true 
    : false 
    : false 
    : false;

// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
) => void
    ? I
    : never;

// credits https://stackoverflow.com/users/125734/titian-cernicova-dragomir
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true;

type Validator<T extends boolean> =
    /**
     * If IsValid returns false | true (boolean) it means Error
     * otherwise - ok
     */
    IsUnion<T> extends true ?
    ['Dear developer, please do smth right']
    /**
     * I'm using empty array here, because 
     * (...flag:[])=>any evaluates to function without arguments
     */
    : []

const foo = <
    Value extends Record<PropertyKey, string>,
    Key extends keyof Value,
    Data extends Entity<Value, Key>[],
>(a: [...Data], ...flag: [...Validator<IsValid<[...Data]>>]) => a

/**
 * Ok
 */
foo([{
    object: { name: 'John' }, key: 'name'
},
{
    object: { surname: 'John' }, key: 'surname'
}])

/**
 * Error
 */
foo([{
    object: { name: 'John' }, key: 'name'
},
{
    object: { surname: 'John' }, key: 'name'
}])

此解决方案由两部分组成:

第 1 部分

我们需要借助可变参数元组类型来推断数组的每个元素 - Data 泛型。 Here,在我的文章中,您可以找到如何操作的说明。

第 2 部分

我们需要检查每个元素是否满足您的要求:ValidatorIsValid 类型的工具。您可以在我的博客 herehere

中找到有关此技术的更多信息