Как написать точную сигнатуру типа для (эквивалента) Function.prototype.bind?

Я пытаюсь создать общую функцию bind для TypeScript, которая будет применять функцию с любым количеством параметров к определенному значению this.

Без типов идея проста:

function bind(self, fn) {
    return (...args) => fn.apply(self, args);
}

Но я не могу связать воедино правильные типы, чтобы удовлетворить систему типов.

function bind<TThis, TFunction extends Function>(self: TThis, fn: TFunction):
  (this: TThis, ...args: Parameters<TFunction>) => ReturnType<TFunction>  {
    return (...args: any[]) => fn.apply(self, args);
}

Я получаю следующую ошибку:

src/shared/common.ts:8:146 - error TS2344: Type 'TFunction' does not satisfy the constraint '(...args: any) => any'.
  Type 'Function' is not assignable to type '(...args: any) => any'.
    Type 'Function' provides no match for the signature '(...args: any): any'.

Определение встроенной функции связывания не поясняет типы, необходимые для ее реализации самостоятельно:

/**
 * For a given function, creates a bound function that has the same body as the original function.
 * The this object of the bound function is associated with the specified object, and has the specified initial parameters.
 * @param thisArg An object to which the this keyword can refer inside the new function.
 * @param argArray A list of arguments to be passed to the new function.
 */
bind(this: Function, thisArg: any, ...argArray: any[]): any;

Мне не нужен ...args при звонке.

Вы можете получить это: tsplay.dev/N7dYRm

ghybs 08.06.2024 12:10

Ваша сигнатура типа неверна: bind на самом деле не сужает диапазон значений, принимаемых как this, а расширяет его.

user3840170 08.06.2024 12:12

Может глупый вопрос, но что не так с родным fn.bind?

Jakub Kotrs 08.06.2024 12:17

@JakubKotrs не глупый вопрос! У меня, наверное, хуже. Я пытаюсь понять, как работать с системой типов.

John Gietzen 08.06.2024 12:21

@user3840170 user3840170 Думаю, да? Может быть? Я создаю совершенно новую сигнатуру функции...

John Gietzen 08.06.2024 12:37
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
5
52
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий
  1. Function действительно отличается от (...args: any[]) => any в TypeScript, как бы странно это ни казалось... См. Разница между `Function` и `(...args: Any[]) => Any`

  2. thisспециальный параметр указывает тип контекста this в теле функции (это то, что вы обеспечиваете с помощью fn.apply) и позволяет TS проверить, что при вызове этой функции контекст также совпадает (чего нам не нужно, потому что контекст уже должен быть привязан! Поэтому его можно вызвать с любым контекстом, последний в любом случае будет проигнорирован). Вместо того, чтобы указывать this для созданной связанной функции, мы можем просто сделать это на входе fn, который нужно привязать.

Итак, мы можем сделать:

function bind<TThis, TFunction extends (this: TThis, ...args: any[]) => any>(self: TThis, fn: TFunction):
    (...args: Parameters<TFunction>) => ReturnType<TFunction> {
    return (...args: any[]) => fn.apply(self, args);
}

И иллюстрируем его использование и влияние на контекст:

function fn(this: { toUpperCase(): string }, name: string) {
    return `${this.toUpperCase()} ${name}`;
}

// The bound function can still accept any context, the latter will be ignored
const boundFn = bind("hello", fn);

const obj = {
    fn,
    boundFn,
    toUpperCase: () => "ignored"
}

console.info(obj.fn("John")); // ignored John
console.info(obj.boundFn("Alice")); // HELLO Alice
console.info(boundFn("Bob")); // HELLO Bob (void context)

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

Я предполагаю, что (...args) => fn.apply(self, args) то же самое, что и fn.bind(self), как указал @JakubKotrs.

John Gietzen 08.06.2024 13:31

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