如何在TypeScript

时间:2019-11-29 07:18:10

标签: typescript

我一直在阅读“编程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上上传了代码

1 个答案:

答案 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_0data_1等。