考虑这样一个函数:
func([
{object: object1, key: someKeyOfObject1},
{object: object2, key: someKeyOfObject2}
])
它有一个数组。我想强制 key
字段保存 object
中保存的对象的键。 每个物体都可以有不同的形状。
为单个对象构造这样的类型很容易:
type Type<T> = { obj: T, key: keyof T }
但是,我不知道如何从中创建一个数组,其中每个元素都将被强制执行。 Type<any>[]
将删除所有类型。
答案 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 部分
我们需要检查每个元素是否满足您的要求:Validator
和 IsValid
类型的工具。您可以在我的博客 here 和 here