我一直在阅读“编程TypeScript”。在本书的一章中,我遇到了这个问题。
我正在尝试实现EventEmitter的包装,该包装对事件名称及其参数有限制。该代码使用泛型来实现可重用性。
书中的代码:
import EventEmitter from 'events'
class SafeEmitter<
Events extends Record<PropertyKey, unknown[]>
> {
private emitter = new EventEmitter
emit<K extends keyof Events>(
channel: K,
...data: Events[K]
) {
return this.emitter.emit(channel, ...data)
}
on<K extends (keyof Events)>(
channel: K,
listener: (...data: Events[K]) => void
) {
return this.emitter.on(channel, listener)
}
}
错误:
(parameter) channel: K extends keyof Events
Argument of type 'K' is not assignable to parameter of type 'string | symbol'.
Type 'keyof Events' is not assignable to type 'string | symbol'.
Type 'string | number | symbol' is not assignable to type 'string | symbol'.
Type 'number' is not assignable to type 'string | symbol'.
Type 'keyof Events' is not assignable to type 'symbol'.
Type 'K' is not assignable to type 'symbol'.
Type 'keyof Events' is not assignable to type 'symbol'.
Type 'string | number | symbol' is not assignable to type 'symbol'.
Type 'string' is not assignable to type 'symbol'.ts(2345)
根据我的快速谷歌搜索,keyof
运算符始终会推断string | symbol | number
。所以我更改了代码以推断字符串。
on<K extends (keyof Events & string)>(
这适用于上面的错误。但是,我还有另一个错误:
(parameter) listener: (...data: Events[K]) => void
Argument of type '(...data: Events[K]) => void' is not assignable to parameter of type '(...args: any[]) => void'.
Types of parameters 'data' and 'args' are incompatible.
Type 'any[]' is not assignable to type 'Events[K]'.ts(2345)
没有类型断言,有没有办法解决这个问题。
(tsconfig.json)
{
"include": ["./src"],
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
}
}
我已经在Codesandbox上上传了代码
答案 0 :(得分:1)
如果您尝试创建类型安全的EventEmitter
实现,这应该可以帮助您入门:
import EventEmitter from "events";
type EventsOf<T> = keyof T & string;
type ListenerFunc<T, E extends EventsOf<T>> =
T[E] extends (...args: any[]) => void ? T[E] : never;
type ListenerArgs<T, E extends EventsOf<T>> =
T[E] extends (...args: infer A) => void ? A : never;
class SafeEmitter<T> extends EventEmitter {
on<E extends EventsOf<T>>(event: E, listener: ListenerFunc<T, E>) {
return super.on(event, listener);
}
once<E extends EventsOf<T>>(event: E, listener: ListenerFunc<T, E>) {
return super.once(event, listener);
}
emit<E extends EventsOf<T>>(event: E, ...args: ListenerArgs<T, E>) {
return super.emit(event, ...args);
}
}
// === Let's test it! ===
interface MyEvents {
started: () => void;
received: (foo: string, bar?: number) => void;
}
class MyEmitter extends SafeEmitter<MyEvents> { }
const emitter = new MyEmitter();
emitter.on("started", () => console.log("started")); // OK
emitter.on("received", (foo, bar) => console.log([foo, bar])); // OK
emitter.on("invalid", () => false); // ERROR
emitter.emit("started"); // OK
emitter.emit("started", "ERROR!"); // ERROR
emitter.emit("received"); // ERROR
emitter.emit("received", "FOO"); // OK
emitter.emit("received", "FOO", 42); // OK
emitter.emit("received", "FOO", "ERROR!"); // ERROR
emitter.emit("invalid"); // ERROR
为回答您的问题,在将any[]
分配给无限制的泛型类型时,TypeScript往往比必要的更为严格。在这种特殊情况下,它无法检测到Events
的属性值必须是数组。
解决这个问题的唯一方法是在Events
接口上将事件侦听器函数定义为实际方法,这正是上面示例中发生的情况。另外,您还可以指定事件参数名称,而不是默认使用data_0
,data_1
等。