TS не может определить соответствующий тип параметра

Подводя итог, я пытаюсь определить функцию типа 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, поэтому я ожидал, что вышеизложенное не вызовет проблем. Что-то не так с тем, что я написал, или это просто проблема с компилятором?

Ох, это было бы намного проще, если бы вы писали TS, а не JS.

jcalz 10.08.2024 16:46
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
0
1
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в том, что функция выводится из параметров, а не наоборот. Что нам нужно сделать, так это определить тип параметра из переданной функции, используя тип утилиты Параметры, из которого можно вывести параметры возвращаемой функции.

// 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 10.08.2024 17:29

Да, @jcalz, если они готовы привести возвращаемое значение, тогда никаких проблем. Я заметил одну вещь: если я попробую подход с as const вместо приведения типа кортежа (с большим количеством параметров, чем первый this), он не сработает. Это потому, что Rest не может работать с массивом только для чтения для его части. Таким образом, приведение к типу кортежа кажется единственным способом реализации этого подхода.

Brother58697 10.08.2024 17:41

Спасибо @Brother58697. Позже я тоже пошел по тому же пути. И, как упомянул @jcalz, я уже ожидал, что кастинг понадобится.

Abdulramon jemil 11.08.2024 05:06

Во время тестирования я понял, что ответ выше не полностью работает, особенно при возврате двух или более параметров. Пример смотрите здесь. bind(foo, () => [null, ""] as [null, ""]) работает (хотя и не полностью), что неправильно, а bind(foo, () => [null, 8] as [null, 8]) не работает, а должно.

Abdulramon jemil 11.08.2024 05:18

@Abdulramonjemil Используйте [null, number] вместо [null, 8]. Приведение к литералу делает его слишком узким для работы Rest.

Brother58697 11.08.2024 05:19

@Brother58697, можешь взглянуть на это, которое я сейчас использую tsplay.dev/NnYVqw Не требует кастинга. Но есть крошечный улов, который можно увидеть на детской площадке.

Abdulramon jemil 11.08.2024 05:26

Также возникают ошибки при передаче неправильных параметров, как показано здесь. Просто выдает ошибку ... may be instantiated with another subtype ... и нужно // @ts-expect-error

Abdulramon jemil 11.08.2024 05:34

Я попробовал, но я в тупике (кстати, мне нравится TupleSlice), может быть, @jcalz сможет обойти эту ошибку.

Brother58697 11.08.2024 06:00

@Abdulramonjemil заставил это работать, рассматривая саму функцию как общий тип и используя Parameters, чтобы использовать более узкий тип для P. Здесь.

Brother58697 11.08.2024 06:13

@Abdulramonjemil Я отредактировал ответ, чтобы отразить правильную причину проблемы.

Brother58697 11.08.2024 06:47

@Brother58697, я видел, что вы использовали never для другой стороны условия для типа параметра возвращаемой функции (где я использовал []). Я не уверен, что лучше, но, похоже, TS не всегда делает вывод [] как тип спреда ...params, как ожидалось. Пример смотрите здесь. Вместо того, чтобы выводить [], он принимает тип Never, что делает функцию невызываемой. Хотя я думаю, что это странное поведение.

Abdulramon jemil 11.08.2024 08:58

Из примера видно, что TS иногда делает вывод [] (первый пример на игровой площадке, указанной выше), а иногда never (второй пример).

Abdulramon jemil 11.08.2024 09:09

@Abdulramonjemil Это хорошая находка. Похоже, что условие переходит в условие else. Я сузил это до союза "" | "a", вызывающего проблему. Оказывается, [число, "а"] не распознается как часть [число, 'а' | ''] , перегрузка не работает, поскольку вместо объединения сигнатур она возвращает сигнатуру последней определенной функции. Поэтому я использовал дистрибутивные условные выражения, чтобы получить объединение кортежей параметров (Spread<Parameters>) и снова итеративно распространить частичное на объединение. Тест

Brother58697 11.08.2024 10:00

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