如何在 TypeScript 中实现类型化的 "both" 函数?
How can I implement a typed "both" function in TypeScript?
故事书 actions 插件提供了一种记录回调调用的便捷方式:
renderButton({onClick: action('onClick')});
action
调用 returns 一个记录字符串 (onClick
) 和调用它的参数的函数。
有时我想同时拥有 action
和其他东西:
renderButton({
onClick: (arg1, arg2, arg3) => {
action('onClick')(arg1, arg2, arg3);
// ... my code
}
});
action
调用变得更加冗长,因为我必须传递所有参数。所以我实现了一个 both
函数让我写:
renderButton({
onClick: both(action('onClick'), (arg1, arg2, arg3) => {
// ... my code
})
});
在我尝试添加 TypeScript 类型之前,这非常有用。这是我的实现:
function both<T extends unknown[], This extends unknown>(
a: (this: This, ...args: T) => void,
b: (this: This, ...args: T) => void,
): (this: This, ...args: T) => void {
return function(this: This, ...args: T) {
a.apply(this, args);
b.apply(this, args);
};
}
此类型在运行时检查并工作,但根据上下文,它会导致不需要的 any
类型或类型错误。例如:
const fnWithCallback = (cb: (a: string, b: number) => void) => {};
fnWithCallback((a, b) => {
a; // type is string
b; // type is number
});
fnWithCallback(
both(action('callback'), (a, b) => {
a; // type is any
b; // type is any
}),
);
fnWithCallback(
both(action('callback'), (a, b) => {
// ~~~~~~~~~~~
// Argument of type '(a: T[0], b: T[1]) => number' is not assignable to
// parameter of type '() => void'.
}),
);
是否可以 both
从回调上下文中正确捕获参数类型?并避免可能来自 action
declaration:
的 any
类型
export type HandlerFunction = (...args: any[]) => void;
这是一个包含完整示例的 playground link。
这应该有效并保持正确的回调参数类型:
type UniversalCallback<Args extends any[]> = (...args: Args) => void;
function both<
Args extends any[],
CB1 extends UniversalCallback<Args>,
CB2 extends UniversalCallback<Args>
>(fn1: CB1, fn2: CB2): UniversalCallback<Args> {
return (...args:Args) => {
fn1(...args);
fn2(...args);
};
}
这个解决方案忽略了 this
但我不知道这对你来说是否有问题,因为你给出的用法示例并没有真正使用 this
.
扩展对将 this
传递给回调的支持非常容易:
type UniversalCallback<T, Args extends any[]> = (this:T, ...args: Args) => void;
function both<
T,
Args extends any[],
CB1 extends UniversalCallback<T, Args>,
CB2 extends UniversalCallback<T, Args>
>(fn1: CB1, fn2: CB2): UniversalCallback<T, Args> {
return function(this:T, ...args:Args) {
fn1.apply(this, args);
fn2.apply(this, args);
};
}
在以下测试中完美运行:
class A { f() {} }
const fnWithCallback = (cb: (this: A, a: string, b: number) => void) => { };
fnWithCallback(function(a, b) {
a; // type is string
b; // type is number
this.f(); // works
});
fnWithCallback(
both(function (a) {
a; // correct type
this.f(); // works
}, function (a, b) {
a; b; // correct types
this.f(); // works
}),
);
故事书 actions 插件提供了一种记录回调调用的便捷方式:
renderButton({onClick: action('onClick')});
action
调用 returns 一个记录字符串 (onClick
) 和调用它的参数的函数。
有时我想同时拥有 action
和其他东西:
renderButton({
onClick: (arg1, arg2, arg3) => {
action('onClick')(arg1, arg2, arg3);
// ... my code
}
});
action
调用变得更加冗长,因为我必须传递所有参数。所以我实现了一个 both
函数让我写:
renderButton({
onClick: both(action('onClick'), (arg1, arg2, arg3) => {
// ... my code
})
});
在我尝试添加 TypeScript 类型之前,这非常有用。这是我的实现:
function both<T extends unknown[], This extends unknown>(
a: (this: This, ...args: T) => void,
b: (this: This, ...args: T) => void,
): (this: This, ...args: T) => void {
return function(this: This, ...args: T) {
a.apply(this, args);
b.apply(this, args);
};
}
此类型在运行时检查并工作,但根据上下文,它会导致不需要的 any
类型或类型错误。例如:
const fnWithCallback = (cb: (a: string, b: number) => void) => {};
fnWithCallback((a, b) => {
a; // type is string
b; // type is number
});
fnWithCallback(
both(action('callback'), (a, b) => {
a; // type is any
b; // type is any
}),
);
fnWithCallback(
both(action('callback'), (a, b) => {
// ~~~~~~~~~~~
// Argument of type '(a: T[0], b: T[1]) => number' is not assignable to
// parameter of type '() => void'.
}),
);
是否可以 both
从回调上下文中正确捕获参数类型?并避免可能来自 action
declaration:
any
类型
export type HandlerFunction = (...args: any[]) => void;
这是一个包含完整示例的 playground link。
这应该有效并保持正确的回调参数类型:
type UniversalCallback<Args extends any[]> = (...args: Args) => void;
function both<
Args extends any[],
CB1 extends UniversalCallback<Args>,
CB2 extends UniversalCallback<Args>
>(fn1: CB1, fn2: CB2): UniversalCallback<Args> {
return (...args:Args) => {
fn1(...args);
fn2(...args);
};
}
这个解决方案忽略了 this
但我不知道这对你来说是否有问题,因为你给出的用法示例并没有真正使用 this
.
扩展对将 this
传递给回调的支持非常容易:
type UniversalCallback<T, Args extends any[]> = (this:T, ...args: Args) => void;
function both<
T,
Args extends any[],
CB1 extends UniversalCallback<T, Args>,
CB2 extends UniversalCallback<T, Args>
>(fn1: CB1, fn2: CB2): UniversalCallback<T, Args> {
return function(this:T, ...args:Args) {
fn1.apply(this, args);
fn2.apply(this, args);
};
}
在以下测试中完美运行:
class A { f() {} }
const fnWithCallback = (cb: (this: A, a: string, b: number) => void) => { };
fnWithCallback(function(a, b) {
a; // type is string
b; // type is number
this.f(); // works
});
fnWithCallback(
both(function (a) {
a; // correct type
this.f(); // works
}, function (a, b) {
a; b; // correct types
this.f(); // works
}),
);