Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript definition for subclass as parameter

I am trying to write a type definition for flummox but I don't fully understand how I am supposed to write it. The gist is that we are supposed to subclass Store, Actions and Flummox classes and pass them to functions.
Here's a code example with the actions:

import { Flummox, Actions, Store } from 'flummox';
class MessageActions extends Actions {
  newMessage(content: string) {
    return content;
  }
 }
class MessageStore extends Store {
   constructor(flux: Flummox) {
   super();

   const messageActions: MessageActions = flux.getActions('messages'); // error here
   this.register(messageActions.newMessage, this.handleNewMessage);
  }
 }

 class Flux extends Flummox {
   constructor() {
     super();

     this.createActions('messages', MessageActions);
     this.createStore('messages', MessageStore, this); //error here
   }
 }

And the definition I started:

/// <reference path="eventemitter3.d.ts"/>

declare module "flummox" {

  type ActionId = string;

  class Store {
    register(action: ActionId | Function, handler: Function): void;
  }

  class Actions {

  }

  class Flummox extends EventEmitter3.EventEmitter.EventEmitter3 {
    createActions(key: string, actions: Actions, constructorArgs?: any | any[]): Actions;
    getActions(key: string): Actions;
    removeActions(key: string): Actions;

    createStore(key: string, store: Store, constructorArgs?: any | any[]): Store;
    getStore(key: string): Store;
    removeStore(key: string): Store;
 }
}

I am getting the following error:

src/app/App.ts(16,11): error TS2322: Type 'Actions' is not assignable to type 'MessageActions'. Property 'newMessage' is missing in type 'Actions'. src/app/App.ts(35,34): error TS2345: Argument of type 'typeof MessageStore' is not assignable to parameter of type 'Store'. Property 'register' is missing in type 'typeof MessageStore'.'.

which is fair enough since I know I should probably use interface but then I won't be able to extend the classes in my code. Here's a link to the repo in case you want to try it

Would anyone be able to help me? I feel like I'm missing something obvious

like image 966
Vincent P Avatar asked Oct 19 '22 10:10

Vincent P


1 Answers

As discussed on IRC, createStore(store: Store) means createStore takes an instance of type Store (or its subtypes), not a type that's a subtype of Store. For the latter, you want store to be of a type that contains a construct signature that returns Store or a subtype of Store. So

createActions(key: string, actions: Actions, constructorArgs?: any | any[]): Actions;

should be either

createActions(key: string, actions: { new(...args: any[]): Actions }, constructorArgs?: any | any[]): Actions;

or

createActions<T extends Actions>(key: string, actions: { new(...args: any[]): T }, constructorArgs?: any | any[]): T;

The latter allows you to return the same type you passed in instead of always returning Actions, should you need it.

The same needs to be done for createStore()


Also

this.register(messageActions.newMessage, this.handleNewMessage);

will cause problems because this will be undefined in handleNewMessage. this.handleNewMessage returns a function, not a bound delegate like in other languages. You either want this.handleNewMessage.bind(this) or message => this.handleNewMessage(message) - the latter form requires you to explicitly write out all the parameters, whereas the former form doesn't but loses typechecking errors if the signatures of the parameter to register and of handleNewMessage ever disagree.

like image 110
Arnavion Avatar answered Oct 22 '22 02:10

Arnavion