// callbacks
const callback1 = (key: string, value: unknown) => {
if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
return value
}
const callback2 = (key: string, value: unknown, min: number, max: number = Infinity) => {
if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
if (value < min || value > max) throw new Error('error')
return value
}
const callback3 = (key: string, value: unknown, something: number) => {
if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
if (value === something) throw new Error('error')
return value
}
type Obj = { [P: string]: unknown }
const tesstobj: Record<string, unknown> = { one: 1, two: 'two' }
// main
const evaluate = <
O extends Obj,
F extends (key: string, val: unknown) => ReturnType<F> // <--
>(obj: O, prop: keyof O, vfunc: F) => {
if (typeof prop !== 'string') throw new Error('error')
return vfunc(prop, obj[prop])
}
const evaluate_two = <
O extends Obj,
F extends (key: string, val: unknown, ...args: unknown[]) => ReturnType<F> // <--
>(obj: O, prop: keyof O, vfunc: F , ...args: unknown[]) => {
if (typeof prop !== 'string') throw new Error('error')
return vfunc(prop, obj[prop], ...args)
}
evaluate(tesstobj, 'one', callback1) // good
evaluate(tesstobj, 'one', callback2) // error as expected
// below expression statements should not fail
evaluate_two(tesstobj, 'one', callback2) // should say missing `min` arg
evaluate_two(tesstobj, 'one', callback2, 1)
evaluate_two(tesstobj, 'one', callback2, 1, 10)
evaluate_two(tesstobj, 'one', callback3, 1) // should say wrong type number instead of string
В приведенном выше фрагменте мне нужно передать callback1
/callback2
/callback3
в параметр обратного вызова evaluate
вместе с его дополнительными аргументами. Я пытался добиться этого в evaluate_two
, но не получилось.
Сообщение об ошибке
Argument of type '(key: string, value: unknown, min: number, max?: number) => number' is not assignable to parameter of type '(key: string, val: unknown, ...args: unknown[]) => number'.
Types of parameters 'min' and 'args' are incompatible.
Type 'unknown' is not assignable to type 'number'.
старая ссылка на игровую площадку
Примечание. Я знаю, что этого можно добиться с помощью объединения, но оно не динамично, что мне не нравится.
Редактировать: вот обновленная ссылка на игровую площадку теперь с пояснениями в ответ на комментарий jcalz.
callback3
, когда передаете именно то, что она ожидает.
@jcalz Верно, я обновил пост. (надеюсь, новый фрагмент прояснит ситуацию)
@jcalz СУПЕРБ, это то, что я искал, спасибо!. (Я запутался с последним аргументом callback3
, думая, что это string
не number
, немного заблудился на детской площадке).
@jcalz Можете ли вы отправить комментарий в качестве ответа, я приму его. Также, если возможно, предложите мне хорошее название вопроса, это поможет будущим странникам.
Я напишу ответ, но, пожалуйста, отредактируйте, чтобы исправить callback3
пример.
Вы можете передать обратный вызов, обновленный код
// callbacks
const callback1 = (key: string, value: unknown) => {
if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
return value
}
const callback2 = (key: string, value: unknown, min: number, max: number = Infinity) => {
if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
if (value < min || value > max) throw new Error('error')
return value
}
const callback3 = (key: string, value: unknown, something: number) => {
if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
if (value === something) throw new Error('error')
return value
}
type Obj = { [P: string]: unknown }
const tesstobj: Record<string, unknown> = { one: 1, two: 'two' }
const evaluate_two = <
O extends Obj,
F1 extends () => ReturnType<F1>
>(obj: O, prop: keyof O, vfunc: F1) => {
if (typeof prop !== 'string') throw new Error('error')
return vfunc();
}
evaluate_two(tesstobj, 'one', () => {
callback1('one', tesstobj['one']);
})
evaluate_two(tesstobj, 'one', () => {
const temp_min = -10;
const temp_max = 10;
callback2('one', tesstobj['one'], temp_min, temp_max);
})
evaluate_two(tesstobj, 'one', () => {
const something = 100;
callback3('one', tesstobj['one'], something);
})
Изменить (обновленный ответ согласно комментарию @bogdanoff):
// callbacks
const callback1 = (key: string, value: unknown) => {
if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
return value
}
const callback2 = (key: string, value: unknown, min: number, max: number = Infinity) => {
if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
if (value < min || value > max) throw new Error('error')
return value
}
const callback3 = (key: string, value: unknown, something: number) => {
if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
if (value === something) throw new Error('error')
return value
}
type Obj = { [P: string]: unknown }
const tesstobj: Record<string, unknown> = { one: 1, two: 'two' }
const evaluate = <
O extends Obj,
F extends (key: string, val: unknown) => ReturnType<F> // <--
>(obj: O, prop: keyof O, vfunc: F) => {
if (typeof prop !== 'string') throw new Error('error')
return vfunc(prop, obj[prop])
}
const evaluate_two = <
O extends Obj,
F extends (key: string, val: unknown, ...args: any[]) => ReturnType<F> // <--
>(obj: O, prop: keyof O, vfunc: F , ...args: any[]) => {
if (typeof prop !== 'string') throw new Error('error')
return vfunc(prop, obj[prop], ...args)
}
evaluate_two(tesstobj, 'one', callback1)
evaluate_two(tesstobj, 'one', callback2)
evaluate_two(tesstobj, 'one', callback3)
просто измените типы аргументов с неизвестного[] на любой[]. Надеюсь это поможет.
Я ценю усилия, но это сводит на нет всю цель. Просто посмотрите на самый первый вызов evaluate_two(
в вашем ответе. Здесь много повторений, например, передача литерала one
в 3 местах, 2 ссылки на переменную tesstobj
. если я хочу изменить имя или значение переменной, мне придется сделать это в нескольких местах, что мне здесь совершенно не нравится.
понял, просто измените тип аргументов с неизвестного[] -> любого[]. См. редактирование в моем ответе выше.
Это близко, но не идеально, обратите внимание на необязательные параметры. проверьте эту игровую площадку, чтобы узнать больше.
Если вы хотите убедиться, что args
соответствует остальным параметрам для F
после key: string, val: unknown
, вам понадобится еще один параметр общего типа для типа args
:
const evaluate_two = <
O extends Obj,
A extends any[],
F extends (key: string, val: unknown, ...args: A) => ReturnType<F>
>(obj: O, prop: keyof O, vfunc: F, ...args: A) => {
if (typeof prop !== 'string') throw new Error('error')
return vfunc(prop, obj[prop], ...args)
}
Теперь вы получаете ожидаемое поведение:
// const callback2: (key: string, value: unknown, min: number, max?: number) => number
evaluate_two(tesstobj, 'one', callback2) // error, too few arguments
evaluate_two(tesstobj, 'one', callback2, 1)
evaluate_two(tesstobj, 'one', callback2, 1, 10)
// const callback3: (key: string, value: unknown, something: string) => string
evaluate_two(tesstobj, 'one', callback3, 1) // error, number is not string
Ссылка на код детской площадки
ОК, я только что заметил небольшую проблему, для этого также требуются дополнительные аргументы, проверьте эту игровую площадку, можно ли это тоже решить?
Нечего «решать», это предполагаемое поведение TypeScript. См. документацию по совместимости функций. Если вам действительно нужно увидеть здесь что-то другое, вам следует рассмотреть возможность открытия нового вопроса (после поиска существующих). Когда я спрашивал вас в комментариях, соответствует ли мой подход вашим потребностям, я хотел, чтобы вы тщательно его проверили, прежде чем сказать мне «да» или «нет». На данный момент это выходит за рамки заданного вопроса.
Спасибо. Мне удалось заставить это работать, как описано здесь.
Решение @jcalz сработало, но была одна проблема: оно позволяло передавать дополнительные аргументы в вызов evaluate_two
, хотя его функция обратного вызова не требовала ни одного, как показано здесь, на игровой площадке.
Мне удалось сделать это правильно, реализовав тип, похожий на тип утилиты Parameters
Typescript, но игнорирующий два запускаемых параметра (a1 и a2) функции.
type ParametersFromIndex2<T extends (a1: any, a2: any, ...args: any) => any> = T extends (a1: any, a2: any, ...args: infer P) => any ? P : never
// callbacks
const callback1 = (key: string, value: unknown) => {
if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
return value
}
const callback2 = (key: string, value: unknown, min: number, max: number = Infinity) => {
if (typeof value !== 'number' || Number.isNaN(value)) throw new Error('error ' + key)
if (value < min || value > max) throw new Error('error')
return value
}
const callback3 = (key: string, value: unknown, something: string) => {
if (typeof value !== 'string') throw new Error('not string')
return value
}
type R = Record<string, unknown>
const tesstobj: R = { one: 1, two: 'two' }
// below type is similar to typescript's `Parameters` type but its ignores startsing 2 args
// type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never
type ParametersFromIndex2<T extends (a1: any, a2: any, ...args: any) => any> = T extends (a1: any, a2: any, ...args: infer P) => any ? P : never
const evaluateNew = <
O extends R,
F extends (k: string, v: unknown, ...args: any) => ReturnType<F>,
>(obj: O, prop: keyof O, callback: F, ...args: ParametersFromIndex2<F>) => {
if (typeof prop !== 'string') throw new Error('error')
return callback(prop, obj[prop], ...args)
}
// testing callback1
evaluateNew(tesstobj, "one", callback1)
evaluateNew(tesstobj, "one", callback1)
evaluateNew(tesstobj, "one", callback1, "error")
evaluateNew(tesstobj, "one", callback1, 1)
// testing callback2
evaluateNew(tesstobj, "one", callback2)
evaluateNew(tesstobj, "one", callback2, 1)
evaluateNew(tesstobj, "one", callback2, 1, 10)
evaluateNew(tesstobj, "one", callback2, 1, 10, "error")
evaluateNew(tesstobj, "one", callback2, 1, 10, "error")
evaluateNew(tesstobj, "one", callback2, "error")
// testing callback3
evaluateNew(tesstobj, "one", callback3)
evaluateNew(tesstobj, "one", callback3, "good")
evaluateNew(tesstobj, "one", callback3, -1)
evaluateNew(tesstobj, "one", callback3, "good", "error")
// above passes all my requirements
Что вы надеетесь выразить там через
unknown[]
? Для типов параметров функции это очень ограничительно, и вам понадобится что-то вродеnever[]
, чтобы сделать его безопасным для типа, илиany[]
, чтобы сделать его менее ограничительным. Посмотрите этот ТАК вопрос и ссылку на игровую площадку. Это полностью решает вопрос? Если да, я напишу ответ или найду подходящую дублирующую цель. Если нет, то что мне не хватает?