我正在尝试将开源库unbounded移植到TypeScript。
它提供了一个binder
函数,该函数采用一个方法,将其与任何参数一起绑定到上下文,并在将.unbounded
设置为原始方法的情况下返回结果。
在JavaScript中是:
function binder (method, context, …args) {
const unbounded = method.unbounded || method
const bounded = method.bind(context, ...args)
Object.defineProperty(bounded, 'unbounded', {
value: unbounded,
enumerable: false,
configurable: false,
writable: false
})
return bounded
}
我所有的尝试都没有达到以下期望:
// arguments should work correctly
function a(this: { local: string }, arg1: string): string {
return this.local + arg1
}
// should fail:
binder(a, null) // context wrong type
binder(a, {local: 123}) // local wrong type
binder(a, {local: 123}, 'bar') // local wrong type
binder(a, {local: 'foo'})() // arg1 is missing
binder(a, {local: 'foo'}, 123) // arg1 wrong type
binder(a, {local: 'foo'})(123) // arg1 wrong type
binder(a, {local: 123}, 123) // local and arg1 wrong type
binder(a, {local: 123})(123) // local and arg1 wrong type
binder(a, {local: 'foo'})('bar', 123) // extra arg
binder(a, {local: 'foo'})('bar', 'baz') // extra arg
// should pass:
binder(a, {local: 'foo'})
binder(a, {local: 'foo'}, 'bar')()
binder(a, {local: 'foo'})('bar')
// return value should work correctly
// should pass
binder(a, {local: 'foo'}, 'bar')().substr(0)
binder(a, {local: 'foo'})('bar').substr(0)
// extensions should work correctly
interface E extends Function {
(this: { local: string }, arg1: string): string
extension?: string
}
const b = function(this: { local: string }, arg1: string): string {
return this.local + arg1
} as E
b.extension = 'hello'
const bb = binder(b, { local: 'foo' }, 'bar')
// bb.extension should not be present
// bb.unbounded.extension should be present
// bb.unbounded.unbounded should not be present
const bbb = binder(bb, { local: 'foo' }, 'bar')
// bbb.extension should not be present
// bbb.unbounded.extension should be present
// bbb.unbounded.unbounded should not be present
我认为TypeScript 3.2 CallbableFunction在这里会有所帮助,但我不知道如何使用它。 It also seems TypeScript has a bug with its implementation that prevents the return types being accurate.
我在做什么错了?
答案 0 :(得分:2)
我们可以采用3.2中引入的bind
的重载,并对其进行修改以转发您添加到函数中的unbounded
属性。
type UnboundFunction<T extends Function, TUnbound> = T & {
unbounded?: TUnbound
}
type BoundFunction<T extends Function, TUnbound> = T & {
unbounded: TUnbound
}
type BoundFunctionHelper<TResult extends Function, TUnbound, TArgument> = BoundFunction<TResult, undefined extends TUnbound ? TArgument : TUnbound >;
export function binder<T, R, TUnbound = undefined>(method: UnboundFunction<(this: T) => R, TUnbound>, thisArg: T): BoundFunctionHelper<() => R, TUnbound, typeof method >;
export function binder<T, A extends any[], R, TUnbound = undefined>(method: UnboundFunction<(this: T, ...args: A) => R, TUnbound>, thisArg: T): BoundFunctionHelper<(...args: A) => R, TUnbound, typeof method >;
export function binder<T, A0, A extends any[], R, TUnbound = undefined>(method: UnboundFunction<(this: T, arg0: A0, ...args: A) => R, TUnbound>, thisArg: T, arg0: A0): BoundFunctionHelper<(...args: A) => R, TUnbound, typeof method >;
export function binder<T, A0, A1, A extends any[], R, TUnbound = undefined>(method: UnboundFunction<(this: T, arg0: A0, arg1: A1, ...args: A) => R, TUnbound>, thisArg: T, arg0: A0, arg1: A1): BoundFunctionHelper<(...args: A) => R, TUnbound, typeof method>
export function binder<T, A0, A1, A2, A extends any[], R, TUnbound = undefined>(method: UnboundFunction<(this: T, arg0: A0, arg1: A1, arg2: A2, ...args: A) => R, TUnbound>, thisArg: T, arg0: A0, arg1: A1, arg2: A2): BoundFunctionHelper<(...args: A) => R, TUnbound, typeof method>;
export function binder<T, A0, A1, A2, A3, A extends any[], R, TUnbound = undefined>(method: UnboundFunction<(this: T, arg0: A0, arg1: A1, arg2: A2, arg3: A3, ...args: A) => R, TUnbound>, thisArg: T, arg0: A0, arg1: A1, arg2: A2, arg3: A3): BoundFunctionHelper<(...args: A) => R, TUnbound, typeof method>;
export function binder<T, AX, R, TUnbound = undefined>(method: UnboundFunction<(this: T, ...args: AX[]) => R, TUnbound>, thisArg: T, ...args: AX[]): BoundFunctionHelper<(...args: AX[]) => R, TUnbound, typeof method>;
export function binder(method: UnboundFunction<Function, Function>, context: any, ...args: any[]): BoundFunction<Function, Function> {
const unbounded = method.unbounded || method
const bounded = method.bind(context, ...args)
Object.defineProperty(bounded, 'unbounded', {
value: unbounded,
enumerable: false,
configurable: false,
writable: false
})
return bounded
}
function a(this: { local: string }, arg: string): string {
return this.local
}
binder(a, null) // fails as this.local is missing
binder(a, {local: 'foo'})() // fails as arg1 is missing
binder(a, {local: 'foo'}, 'bar')() // pass
binder(a, {local: 'foo'})('bar') // pass
binder(a, {local: 'foo'}, 123)() // fail due to incorrect type
binder(a, {local: 123}, 'bar')() // fail due to incorrect type
binder(a, {local: 'foo'})('bar') // fail due to incorrect type
binder(a, {local: 'foo'})('bar', 'bob') // fail due to extra arg
binder(a, {local: 'foo'}, 'bar', 'bob') // unfortunately works, also a problem with the 3.2 implementation of bind.
// Preserve unbounded type test
function a2(this: { local: string }, arg: string, arg2: string): string {
return this.local
}
var unbounded = binder(binder(a2, { local: 'foo'}), undefined, "").unbounded;
var o = { local : "", unbounded };
o.unbounded("", "") // unbounded type reserved trhough mutiple binder calls