在子类或接口上定义的 Typescript 专用重载

Typescript specialized overloads defined on a subclass or interface

有没有一种方法可以使下面的工作不必在子类中定义一个简单地调用超类或不必要地重复非专用签名的实现?

class Emitter {
    on(name: 'one', handler: (value: number) => void): void;
    on(name: string, handler: (...args: any[]) => void): void;
    on(name: string, handler: (...args: any[]) => void): void { 
        // do stuff
    }
}


class Subclass extends Emitter {
    on(name: 'two', handler: (value: string) => void): void;
    on(name: string, handler: (...args: any[]) => void): void;
    // error no implementation specified
}


interface IEmitter {
    on(name: 'one', handler: (value: number) => void): void;
    on(name: string, handler: (...args: any[]) => void): void;
}


interface ISubclass extends IEmitter {
    on(name: 'two', handler: (value: string) => void): void;
    // error overload not assignable to non specialized
}

函数重载只有在它们是对象类型的调用签名时才会合并。最简单的修复(对于接口案例)是将函数类型分离到它自己的接口中并扩展它:

interface EmitterEvent {
    (name: 'one', handler: (value: number) => void): void;
    (name: string, handler: (...args: any[]) => void): void;
}

interface SubclassEmitterEvent extends EmitterEvent {
    (name: 'two', handler: (value: string) => void): void;
}

interface IEmitter {
    on: EmitterEvent;
}

interface ISubclass extends IEmitter {
    on: SubclassEmitterEvent;
}

var x: ISubclass;
x.on('one', n => n.toFixed()); // n: number
x.on('two', s => s.substr(0)); // s: string
var y: IEmitter;
y.on('two', a => a); // a: any

class 情况下的等效版本需要一些工作(假设您关心原型上的函数——如果不是,只需使用函数表达式作为 on 的初始值设定项) ):

class Emitter {
    on: EmitterEvent;
}
module Emitter {
    Emitter.prototype.on = function(name: string, handler: any) {
        // Code here
    }
}

似乎在 TS 1.5 中,模块技巧(现在称为名称空间)将不起作用,因为它抱怨 'this'。

下面是我正在使用的工作方法:

interface EventEmitterOn {
    (event: string, listener: () => void);
}

interface FooEventEmitterOn extends EventEmitterOn {
    (event: 'dependency', listener: (dep: string[]) => void);
}

class EventEmitter {
    on: EventEmitterOn;
}

EventEmitter.prototype.on = function(event, listener) {
    // implementation
}

class Foo extends EventEmitter {
    on: FooEventEmitterOn;
}

var foo = new Foo
// error
foo.on('dependency', function(dep: number) {})
// ok
foo.on('dependency', function(dep: string[]) {})

要获得更简洁的解决方案,请查看 ee-ts。它提供类型化的 EventEmitter class 并支持:

  • 严格的事件名称(想想 emit(type, ...args)typestring 联合)

  • 类型检查 emiton 调用(再也不会发出错误的类型或在侦听器中期望错误的类型)

import { EventEmitter as EE } from 'ee-ts'

type User = { name: string }

// All possible events must be explicitly defined as methods here.
// The return type can be non-void because the `emit` method returns the last non-void value.
// The return type can never be required, because `void` is implicitly added to every event.
interface Events {
  login(user: User): void
  logout(): string
}

// Make your subclass generic to let users add their own events.
class App<T = {}> extends EE<T & Events> {
  /* ... */
}

let app = new App()

// The type of `user` is inferred.
app.on('login', user => {
  console.log(user.name) // user.name is string
})

// Invalid argument types are caught.
app.one('login', (invalid: boolean) => {}) // [ts] Type 'User' is not assignable to type 'boolean'.

// Invalid return values are caught.
app.one('logout', () => true) // [ts] Type 'boolean' is not assignable to type 'string | void'.

// Unknown event names are caught.
app.emit('invalid') // [ts] Argument of type '"invalid"' is not assignable to parameter of type '"login" | "logout"'.