Я пытаюсь создать общую функцию 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
при звонке.
Ваша сигнатура типа неверна: bind
на самом деле не сужает диапазон значений, принимаемых как this
, а расширяет его.
Может глупый вопрос, но что не так с родным fn.bind
?
@JakubKotrs не глупый вопрос! У меня, наверное, хуже. Я пытаюсь понять, как работать с системой типов.
@user3840170 user3840170 Думаю, да? Может быть? Я создаю совершенно новую сигнатуру функции...
Function
действительно отличается от (...args: any[]) => any
в TypeScript, как бы странно это ни казалось... См. Разница между `Function` и `(...args: Any[]) => Any`
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.
Вы можете получить это: tsplay.dev/N7dYRm