Я хочу сделать что-то вроде этого:
type Item<T> = T & {
func: (v: T) => void
}
function a<What>(...items: Item<What>[]) {}
a({
name: "",
func: (v: {name: string}) => {}
}, {
count: 0,
func: (v: {count: number}) => {}
})
Однако приведенный выше код вызовет ошибку, как сделать то же самое с типскриптом?
ах, это отличается для каждого элемента?
Да, как видите, What — это общий параметр.






type Item<T> = T & {
func: (v: T) => void
}
function a<A extends Item<any>[]>(...items: A) {}
a({
name: "",
func: (v: {name: string}) => {}
}, {
count: 0,
func: (v: {count: number}) => {}
})
// ^?
// function a<[{
// name: string;
// func: (v: {
// name: string;
// }) => void;
// }, {
// count: number;
// func: (v: {
// count: number;
// }) => void;
// }]>(items_0: {
// name: string;
// func: (v: {
// name: string;
// }) => void;
// }, items_1: {
// count: number;
// func: (v: {
// count: number;
// }) => void;
// }): void
Это неправильно, это не гарантирует, что каждый аргумент удовлетворяет ограничению Item, Item<any> сворачивается в any, поэтому вы, по сути, говорите, function a<A extends any[]>(...items: A){}
У меня почти есть это, однако я не могу получить функцию, чтобы вывести общий вывод сам по себе, но я все равно делюсь этим, на случай, если кто-то другой может решить это, или если это все же поможет вам.
Трюк, который я использовал, заключается в рекурсивном выводе каждого элемента What, таким образом, What правильно обрабатывается как кортеж, а ограничение элемента применяется к каждому элементу в What отдельно.
type Item<T> =
T & {func(v:T):void}
type Items<What> =
What extends [Item<infer A>, ...infer Rest]
? [Item<A>,...Items<[...Rest]>]
: What extends [] ? [] : never
function testFunction<What>(...items: Items<What>) {}
//this works, with an explicit generic
testFunction<[Item<{
name: string;
}>, Item<{
count: number;
}>]>({
name: "",
func: (v: {name: string}) => {}
}, {
count: 0,
func: (v: {count: number}) => {}
})
Вы можете использовать такой синтаксис:
function a<T extends any[]>(...items: {
[K in keyof T]: T[K] & { func: (v: Omit<T[K], 'func'>) => void }
}) {}
Сопоставленный тип можно использовать для сопоставления каждого элемента в items. T[K] всегда представляет текущий элемент, поэтому мы можем использовать его для пересечения себя с объектом функции.
// ok
a({
name: "",
func: (v: { name: string }) => {}
}, {
count: 0,
func: (v: { count: number }) => {}
})
// error
a({
name: 0,
func: (v: { name: string }) => {} /*
~~~~ Types of property 'name' are incompatible
*/
}, {
count: 0,
func: (v: { count: number }) => {}
})
Для реализации функции я бы рекомендовал поместить все сложные универсальные вещи в перегрузку и использовать более простую сигнатуру для реализующей функции.
function b<T extends any[]>(...items: {
[K in keyof T]: T[K] & { func: (v: Omit<T[K], 'func'>) => void }
}): void
function b(...items: ({ func: (arg: object) => void } & Record<string, any>)[]) {
for (const item of items) {
item.func({})
}
}
как ни странно, ваше решение позволяет передать дополнительное свойство func в v, func: (v: { name: string, func: any }) => {}, но запрещает все другие свойства, также внутри функции a элементы элементов выводятся как any из-за подписи индекса, но это, по крайней мере, лучше, чем мое решение, которое выводит их как never 😅
Да, наверное, нам стоит Omit функцию из T[K].
Что должно быть
Whatв вашем случае?