Typescript specialized overloads defined on a subclass or interface

后端 未结 3 935
南笙
南笙 2020-12-19 11:23

Is there a way to make the following work without having to define an implementation in the subclass which simply calls the superclass or unnecessarily repeats the non-speci

相关标签:
3条回答
  • 2020-12-19 11:55

    Seems that with TS 1.5 the module trick (which is now called a namespace) won't work, as it complains about 'this'.

    Below a working approach which I'm using:

    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[]) {})
    
    0 讨论(0)
  • 2020-12-19 12:08

    Function overloads only get combined if they are call signatures on an object type. The easiest fix (for the interface case) is to separate out the function type into its own interface and extend that:

    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
    

    The equivalent version in the class case takes some work (assuming you care about the function going on the prototype -- if not, just use a function expression as an initializer for on instead):

    class Emitter {
        on: EmitterEvent;
    }
    module Emitter {
        Emitter.prototype.on = function(name: string, handler: any) {
            // Code here
        }
    }
    
    0 讨论(0)
  • 2020-12-19 12:10

    For a less verbose solution, check out ee-ts. It provides a typed EventEmitter class with support for:

    • strict event names (think string unions for the type in emit(type, ...args))

    • type-checked emit and on calls (never again emit the wrong type or expect the wrong types in a listener)

     

    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"'.
    
    0 讨论(0)
提交回复
热议问题