我正在尝试在 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; }'.
},
)
您确实希望每个参数都有其自己的类型参数以放入TYPE<?>
中。如果您只使用一个,那么最多您将获得所有预期类型参数的union,然后它将开始接受您不想要的东西。 表示此约束的方法是在所有预期类型参数的
元组类型
中创建
fn2
generic,然后将其映射到函数参数的元组:
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}>
(失败)。