为什么此泛型函数中的嵌套对象数组的类型推断失败?

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

我正在尝试在 TypeScript 中创建一个强类型函数,以确保单个对象和对象数组中两个属性

attrA
attrB
之间的一致性。这是我的设置:

type PARAM = { [k: string]: string }

type TYPE<T extends PARAM> = {
  attrA: T,
  attrB: { [k in keyof T]: null },
}

// This works as expected
function fn1<T extends PARAM>(a: TYPE<T>) { }

fn1({
  attrA: { a: 'a' },
  attrB: { a: null } // OK because 'a' is in attrA
})

fn1({
  attrA: { b: 'b' },
  attrB: { z: null } // Error: Property 'z' does not exist on type '{ b: null; }'.
})

但是,当我尝试将此行为扩展到数组时,类型检查失败:

function fn2<T extends PARAM>(a: TYPE<T>[]) { }

fn2([
  {
    attrA: { a: 'a' },
    attrB: { a: null } 
  }, {
    attrA: { b: 'b' },
    attrB: { z: null } // should be an error but it's not
  },
])

在第二个示例中,我希望

attrB
键始终与数组的每个元素的
attrA
键匹配。但是,TypeScript 不会因不匹配的键引发错误(
z
不在
attrA
中)。

为什么 TypeScript 无法对数组强制执行此类型约束?如何修复它,以便

fn2
正确验证数组中每个对象的
attrA
attrB
之间的关系?


更新:

如果我定义几个通用的Tⁿ就可以了,但是这是不可行的,这需要动态完成

function fn2<T1 extends PARAM, T2 extends PARAM>(...a: [TYPE<T1>, TYPE<T2>]) { }

fn2(
  {
    attrA: { a: 'a' },
    attrB: { a: null }
  },
  {
    attrA: { b: 'b' },
    attrB: { z: null } // Error: Property 'z' does not exist on type '{ a: null; }'.
  },
)
arrays typescript generics
1个回答
1
投票

您确实希望每个参数都有其自己的类型参数以放入TYPE<?>

中。如果您只使用一个,那么最多您将获得所有预期类型参数的
union,然后它将开始接受您不想要的东西。

表示此约束的方法是在所有预期类型参数的

元组类型

中创建
fn2generic,然后将其映射到函数参数的元组

function fn2<T extends PARAM[]>(a: { [I in keyof T]: TYPE<T[I]> }) { }
这里 

a

 属于映射类型 
{[I in keyof T]: TYPE<T[I]>}
,因此如果 
T
[X, Y, Z]
,那么 
a
 属于 
[TYPE<X>, TYPE<Y>, TYPE<Z>]
 类型。我们来测试一下:

fn2([ { attrA: { a: 'a' }, attrB: { a: null } // okay }, { attrA: { b: 'b' }, attrB: { z: null } // error! }, ]) // <[{ a: string; }, { b: string; }]>
看起来不错。类型参数 

T

 被推断为 
[{a: string}, {b: string}]
,因此根据需要检查第一个参数 
TYPE<{a: string}>
(成功),第二个参数检查 
TYPE<{b: string}>
(失败)。

Playground 代码链接

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