Подводя итог, я пытаюсь определить функцию типа function.prototype.bind()
, но которая использует функцию следующим образом:
/**
* @template T
* @template {any[]} A
* @template {any[]} B
* @template R
* @param {(this: T, ...params: [...A, ...B]) => R} func
* @param {() => [T, ...A]} getParams
*/
export function bindReturn(func, getParams) {
/** @param {B} params */
return (...params) => func.call(...getParams(), ...params)
}
Однако, когда я использую его следующим образом, TS не может правильно определить тип параметра B
/**
* @param {string} a
* @param {number} b
* @param {boolean} c
*/
function f(a, b, c) {
return /** @type {const} */ ([a, b, c])
}
const f2 = bindReturn(f, () => /** @type {const} */ ([null]))
Здесь параметр типа B
из bindReturn
выводится как any[]
вместо [a: string, b: number, c: boolean]
. Я использовал [...A, ...B]
, потому что он также используется в определении .bind()
в lib.d.ts
, поэтому я ожидал, что вышеизложенное не вызовет проблем. Что-то не так с тем, что я написал, или это просто проблема с компилятором?
Проблема в том, что функция выводится из параметров, а не наоборот. Что нам нужно сделать, так это определить тип параметра из переданной функции, используя тип утилиты Параметры, из которого можно вывести параметры возвращаемой функции.
// Borrowed from OP's comment/appraoch
export type TupleSlice<T extends any[]> =
T extends [infer First, ...infer Rest]
? [] | [First, ...TupleSlice<Rest>]
: [];
function bind<
T,
A extends (...params: any[]) => any,
B extends TupleSlice<Parameters<A>>
>(
func: A,
getParams: () => [T, ...B]
) {
return (...params: Parameters<A> extends [...B, ...infer I] ? I : never) => func.call(...getParams(), ...params) as ReturnType<A>;
}
function foo(a: number, b: string) { }
const foo2 = bind(foo, () => [null])
foo2(1, 'foo') // (a: number, b: string)
Примечание. В этом ответе изначально использовался общий тип Rest<Tuple, Partial>
для получения кортежа остальных параметров, но для этого потребовалось приведение кортежа, возвращаемого getParams
, отсюда и комментарии о приведении. Непосредственное определение и вывод параметров возвращаемой функции с условным типом (также из подхода OP в комментариях) позволяет обойти это.
Я думаю, что написание вопроса OP () => /** @type {const} */ ([null])
эквивалентно () => ([null] as const)
, что тоже работает, и поэтому в вопросе уже ожидается «подвох». Но мне не хватает опыта работы с JSDoc.
Да, @jcalz, если они готовы привести возвращаемое значение, тогда никаких проблем. Я заметил одну вещь: если я попробую подход с as const
вместо приведения типа кортежа (с большим количеством параметров, чем первый this
), он не сработает. Это потому, что Rest
не может работать с массивом только для чтения для его части. Таким образом, приведение к типу кортежа кажется единственным способом реализации этого подхода.
Спасибо @Brother58697. Позже я тоже пошел по тому же пути. И, как упомянул @jcalz, я уже ожидал, что кастинг понадобится.
Во время тестирования я понял, что ответ выше не полностью работает, особенно при возврате двух или более параметров. Пример смотрите здесь. bind(foo, () => [null, ""] as [null, ""])
работает (хотя и не полностью), что неправильно, а bind(foo, () => [null, 8] as [null, 8])
не работает, а должно.
@Abdulramonjemil Используйте [null, number]
вместо [null, 8]
. Приведение к литералу делает его слишком узким для работы Rest
.
@Brother58697, можешь взглянуть на это, которое я сейчас использую tsplay.dev/NnYVqw Не требует кастинга. Но есть крошечный улов, который можно увидеть на детской площадке.
Также возникают ошибки при передаче неправильных параметров, как показано здесь. Просто выдает ошибку ... may be instantiated with another subtype ...
и нужно // @ts-expect-error
Я попробовал, но я в тупике (кстати, мне нравится TupleSlice
), может быть, @jcalz сможет обойти эту ошибку.
@Abdulramonjemil заставил это работать, рассматривая саму функцию как общий тип и используя Parameters
, чтобы использовать более узкий тип для P. Здесь.
@Abdulramonjemil Я отредактировал ответ, чтобы отразить правильную причину проблемы.
@Brother58697, я видел, что вы использовали never
для другой стороны условия для типа параметра возвращаемой функции (где я использовал []
). Я не уверен, что лучше, но, похоже, TS не всегда делает вывод []
как тип спреда ...params
, как ожидалось. Пример смотрите здесь. Вместо того, чтобы выводить []
, он принимает тип Never, что делает функцию невызываемой. Хотя я думаю, что это странное поведение.
Из примера видно, что TS иногда делает вывод []
(первый пример на игровой площадке, указанной выше), а иногда never
(второй пример).
@Abdulramonjemil Это хорошая находка. Похоже, что условие переходит в условие else. Я сузил это до союза "" | "a"
, вызывающего проблему. Оказывается, [число, "а"] не распознается как часть [число, 'а' | ''] , перегрузка не работает, поскольку вместо объединения сигнатур она возвращает сигнатуру последней определенной функции. Поэтому я использовал дистрибутивные условные выражения, чтобы получить объединение кортежей параметров (Spread<Parameters>
) и снова итеративно распространить частичное на объединение. Тест
Ох, это было бы намного проще, если бы вы писали TS, а не JS.