Как назначить тип функции, которая имеет разные/дополнительные аргументы в машинописном тексте

// 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.

Что вы надеетесь выразить там через unknown[]? Для типов параметров функции это очень ограничительно, и вам понадобится что-то вроде never[], чтобы сделать его безопасным для типа, или any[], чтобы сделать его менее ограничительным. Посмотрите этот ТАК вопрос и ссылку на игровую площадку. Это полностью решает вопрос? Если да, я напишу ответ или найду подходящую дублирующую цель. Если нет, то что мне не хватает?

jcalz 19.04.2024 14:10
Я бы сделал это по этой ссылке на игровую площадку, но я не понимаю, почему вы ожидаете ошибку callback3, когда передаете именно то, что она ожидает.
jcalz 19.04.2024 17:06

@jcalz Верно, я обновил пост. (надеюсь, новый фрагмент прояснит ситуацию)

bogdanoff 19.04.2024 17:11

@jcalz СУПЕРБ, это то, что я искал, спасибо!. (Я запутался с последним аргументом callback3, думая, что это string не number, немного заблудился на детской площадке).

bogdanoff 19.04.2024 17:21

@jcalz Можете ли вы отправить комментарий в качестве ответа, я приму его. Также, если возможно, предложите мне хорошее название вопроса, это поможет будущим странникам.

bogdanoff 19.04.2024 17:23

Я напишу ответ, но, пожалуйста, отредактируйте, чтобы исправить callback3 пример.

jcalz 19.04.2024 17:25
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
6
90
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы можете передать обратный вызов, обновленный код

// 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. если я хочу изменить имя или значение переменной, мне придется сделать это в нескольких местах, что мне здесь совершенно не нравится.

bogdanoff 19.04.2024 12:10

понял, просто измените тип аргументов с неизвестного[] -> любого[]. См. редактирование в моем ответе выше.

Shubham 19.04.2024 13:24

Это близко, но не идеально, обратите внимание на необязательные параметры. проверьте эту игровую площадку, чтобы узнать больше.

bogdanoff 19.04.2024 16:44

Если вы хотите убедиться, что 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

Ссылка на код детской площадки

ОК, я только что заметил небольшую проблему, для этого также требуются дополнительные аргументы, проверьте эту игровую площадку, можно ли это тоже решить?

bogdanoff 19.04.2024 17:34

Нечего «решать», это предполагаемое поведение TypeScript. См. документацию по совместимости функций. Если вам действительно нужно увидеть здесь что-то другое, вам следует рассмотреть возможность открытия нового вопроса (после поиска существующих). Когда я спрашивал вас в комментариях, соответствует ли мой подход вашим потребностям, я хотел, чтобы вы тщательно его проверили, прежде чем сказать мне «да» или «нет». На данный момент это выходит за рамки заданного вопроса.

jcalz 19.04.2024 17:37

Спасибо. Мне удалось заставить это работать, как описано здесь.

bogdanoff 20.04.2024 07:11
Ответ принят как подходящий

Решение @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

Ссылка на игровую площадку

Другие вопросы по теме