问题描述
首先:抱歉这篇长文章,事实上我在这里有两个不同的问题。但由于两者之间的关系如此密切(实现相同目标的两种不同方法),我认为最好将它们放在一个帖子中。
我正在尝试实现一个轻量级的 TypeScript 信号/事件框架,其中
我知道存在一些以某种方式实现类似目标的解决方案,但我有一个特定的 简单易用的设计利用了较新的 TypeScript 通用索引类型约束功能 - 但现在我显然无法自己掌握它们:)
我使用的是 TypeScript 4.2.3。
我尝试了两种不同的方法,但结果不同......
第一种方法
这可以编译,但我们在信号参数和返回值上没有任何类型:
// Define signals by name and a function,which specifies SignalArgs
// and SignalValue (return value).
type Signals = {
hello: (name: string) => string;
add: (val1: number,val2: number) => number;
};
// Generic class which should implement on() and emit() with arguments
// bound to the specific signals enumerated in the Signals type.
class SignalBus<Signals> {
on<Key extends string & keyof Signals,Spec extends (...args: any) => any & Signals[Key]>(
signal: Key,handler: (
signal: Signal<Parameters<Spec>,ReturnType<Spec>>,...args: Parameters<Spec>
) => Promise<ReturnType<Spec>>,): SignalConnection<Parameters<Spec>,ReturnType<Spec>> {
// ...
console.log("on",signal,handler);
const connection = (handler as unkNown) as SignalConnection<Parameters<Spec>,ReturnType<Spec>>;
connection.signal = signal;
return connection;
}
async emit<Key extends string & keyof Signals,...args: Parameters<Spec>
): Promise<Signal<Parameters<Spec>,ReturnType<Spec>>> {
// ...
console.log("emit",args);
return new Signal<Parameters<Spec>,ReturnType<Spec>>(signal,args);
}
}
// Signal objects represent a signal at runtime
class Signal<SignalArgs extends unkNown[] = [],SignalValue = any> {
public value: SignalValue;
constructor(public signal: string,public args: SignalArgs) {}
}
// SignalConnection objects enclose the handler callback,signal name and probably more
type SignalConnection<SignalArgs extends unkNown[],SignalResult> = {
(signal: Signal<SignalArgs,SignalResult>,...args: SignalArgs): Promise<SignalResult | void>;
signal: string;
};
// Create a SignalBus specific to the Signals defined above
const bus = new SignalBus<Signals>();
// Fine
bus.on("hello",async (_signal,name) => `Hello ${name}`);
// Should give type error,because two numeric arguments and numeric return value are expected
bus.on("add",name: string) => `Shouldn't be accepted ${name}`);
// Should give type error,because only one string argument is expected
bus.emit("hello","World","shouldn't be accepted");
on<Key extends string & keyof Signals,Spec extends (...args: any) => any & Signals[Key]>(...)
使用 Spec extends (...args: any) => any
会发生这种情况。
我知道我必须将 Signals[Key]
属性限制为可调用的
函数使 Parameters<>
和 ReturnValue<>
实用程序类型起作用。
但是如何在不丢失 Signals[Key]
函数的类型信息的情况下做到这一点?
这里我定义了一个通用的 signal<>
类型,它由信号参数和返回值类型组成,并通过索引访问它们 - 但也没有运气......
当使用 SignalBus 类时,编译器会抱怨不可分配的 never
类型。查看代码示例底部的注释:
// Generic type to specify signal arguments and return value.
type signal<SignalArgs extends unkNown[] = [],SignalResult = void> = {
args: SignalArgs;
result: SignalResult;
};
// Define signals by name and the generic signal<> type.
type Signals = {
hello: signal<[name: string],string>;
add: signal<[val: number,val2: number],string>;
};
// Generic class which should implement on() and emit() with arguments
// bound to the specific signals enumerated in the Signals type.
class SignalBus<Signals> {
on<Key extends string & keyof Signals,Spec extends signal & Signals[Key]>(
signal: Key,handler: (
signal: Signal<Spec["args"],Spec["result"]>,...args: Spec["args"]
) => Promise<Spec["result"]>,): SignalConnection<Spec["args"],Spec["result"]> {
// ...
}
async emit<Key extends string & keyof Signals,...args: Spec["args"]
): Promise<Signal<Spec["args"],Spec["result"]>> {
// ...
}
}
// Signal objects represent a signal at runtime
class Signal<SignalArgs extends unkNown[] = [],...args: SignalArgs): Promise<SignalResult | void>;
signal: string;
};
// Create a SignalBus specific to the Signals defined above
const bus = new SignalBus<Signals>();
// Type 'Promise<string>' is not assignable to type 'Promise<never>'.
// Type 'string' is not assignable to type 'never'. ts(2322)
bus.on("hello",name) => `Hello ${name}`);
// Argument of type '(_signal: Signal<never,never>,name: string) => Promise<string>' is not assignable to parameter of type '(signal: Signal<never,...args: never) =>
// Promise<never>'.
// Type 'Promise<string>' is not assignable to type 'Promise<never>'. ts(2345)
bus.on("add",name: string) => `Shouldn't be accepted ${name}`);
// Argument of type 'string' is not assignable to parameter of type 'never'. ts(2345)
bus.emit("hello","shouldn't be accepted");
看起来类型约束 Spec extends signal & Signals[Key]
从来没有
匹配。为什么?
有人对此有任何想法吗?
解决方法
由于泛型之一中的以下扩展子句,该方法不起作用:
Spec extends (...args: any) => any & Signals[Key]
由于示例中的 Signals
应该始终是键/函数对的集合,因此它最终将被评估为如下所示:
Spec extends (...args: any) => any & ((exampleArg: string) => string)
(...args: any) => any & ((exampleArg: string) => string)
的交集将始终为 (...args: any) => any
。这对于任何其他可能的函数都是正确的。需要的是将 Signals
限制为始终是键/函数对的集合。因此,使此示例工作所需的更改如下:
// add restriction to Signals
class SignalBus<Signals extends Record<string,(...args: any[]) => ()>> {
// remove (...args: any[]) => any &
on<Key extends string & keyof Signals,Spec extends Signals[Key]>(
signal: Key,handler: (
signal: Signal<Parameters<Spec>,ReturnType<Spec>>,...args: Parameters<Spec>
) => Promise<ReturnType<Spec>>,): SignalConnection<Parameters<Spec>,ReturnType<Spec>> {
// ...
console.log("on",signal,handler);
const connection = (handler as unknown) as SignalConnection<Parameters<Spec>,ReturnType<Spec>>;
connection.signal = signal;
return connection;
}
// remove (...args: any) => any &
async emit<Key extends string & keyof Signals,...args: Parameters<Spec>
): Promise<Signal<Parameters<Spec>,ReturnType<Spec>>> {
// ...
console.log("emit",args);
return new Signal<Parameters<Spec>,ReturnType<Spec>>(signal,args);
}
}