【问题标题】:Typescript specialized overloads defined on a subclass or interface在子类或接口上定义的 Typescript 专用重载
【发布时间】:2015-02-24 18:33:48
【问题描述】:

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

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
}

【问题讨论】:

    标签: typescript


    【解决方案1】:

    仅当函数重载是对象类型的调用签名时才会合并。最简单的解决方法(对于接口情况)是将函数类型分离到它自己的接口中并扩展它:

    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
    

    类案例中的等效版本需要一些工作(假设您关心原型上的函数 - 如果不关心,只需使用函数表达式作为 on 的初始化器):

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

    【讨论】:

    • 谢谢瑞恩!这种方法是可行的,但与在接口上声明方法重载(ala addEventListener)有很大不同。未来版本的 TypesScript 是否有可能允许类上的方法重载以重用基类实现(只要它兼容)?
    • 不太可能,因为这需要一些新的语法或重大更改。
    • 函数在原型上的版本有个缺点,函数不能调用同一个类的其他私有函数。
    【解决方案2】:

    似乎在 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[]) {})
    

    【讨论】:

      【解决方案3】:

      如需更简洁的解决方案,请查看ee-ts。它提供了一个类型化的EventEmitter 类,支持:

      • 严格的事件名称(想想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"'.
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-07-27
        • 2017-02-19
        • 2018-01-10
        • 2019-01-13
        • 1970-01-01
        • 2021-07-06
        相关资源
        最近更新 更多