Skip to content

Use string literal types to get rid of redundant overload definitions #6251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
kimamula opened this issue Dec 26, 2015 · 3 comments
Closed
Labels
Question An issue which isn't directly actionable in code

Comments

@kimamula
Copy link
Contributor

Inspired by #1003 and #6028.
Isn't it possible to use string literal types for sharing specialized signatures among several methods?

i.e., I would like to write:

type TodoEvent = {
  name: 'create';
  payload: string;
} | {
  name: 'updateText';
  payload: {
    id: string;
    text: string;
  };
};

interface TodoEventEmitter {
  emit(name: TodoEvent.name, payload: TodoEvent.payload): boolean;
  on(name: TodoEvent.name, listener: (payload: TodoEvent.payload) => any): this;
  // ...etc
}

const eventEmitter: TodoEventEmitter = new EventEmitter();

instead of:

interface TodoEventEmitter {
  emit(name: 'create', payload: string): boolean;
  emit(name: 'updateText', payload: { id: string; text: string; }): boolean;
  emit(name: string, payload: any): boolean;

  on(name: 'craete', listener: (payload: string) => any): this; // typo 'craete', but no error
  on(name: 'updateText', listener: (payload: { id: number; text: string; }) => any): this; // id type incorrect, but no error
  on(name: string, listener: (payload: any) => any): this;

  // similar definitions for other methods again and again and again
}

const eventEmitter: TodoEventEmitter = new EventEmitter();

Overload definitions would be further reusable when used in combination with generic types.

interface EventEmitterBase<T extends {name: string, payload: any}> {
  // ...
}

const myEventEmitter: EventEmitterBase<MyEvent> = new EventEmitter();
const anotherEventEmitter: EventEmitterBase<AnotherEvent> = new EventEmitter();
@DanielRosenwasser
Copy link
Member

What you typed out isn't valid in its semantics, but you can do something like this:

namespace TodoEvent {
    export type Name = "create" | "updateText";
    export type Payload = string | { id: string; text: string };
}

interface TodoEventEmitter {
  emit(name: TodoEvent.Name, payload: TodoEvent.Payload): boolean;
  on(name: TodoEvent.Name, listener: (payload: TodoEvent.Payload) => any): this;
  // ...etc
}

Though that won't do a ton for you.

Why don't you give string literal types a try by installing our nightlies? Run npm install -g typescript@next.

@DanielRosenwasser DanielRosenwasser added the Question An issue which isn't directly actionable in code label Dec 27, 2015
@kimamula
Copy link
Contributor Author

Thanks for a reply, @DanielRosenwasser.

Well, this is a suggestion.
It's common that multiple methods should share the definition of the specialized signatures, such as the methods of EventEmitter as I described above, as well as addEventListener/removeEventListener of HTMLElement, and createElement/getElementsByTagName of Document.
Unfortunately however, currently we have to repeat the same definition for each method in such situations (AFAIK).

I think it'd be really nice if the functionality of string literal types could be extended to resolve this.

In your example, the event "create" is not linked to the specific type of payload.
So one can emit the "create" event with a payload of either string or { id: string; text: string }, which is not safe.

@saschanaz
Copy link
Contributor

This would be able to be expressed with extended #1295 proposal as:

interface TodoEventTypes {
    'create':  { payload: string; };
    'updateText': {
        payload: {
            id: string;
            text: string;
        }
    }
}

interface TodoEventEmitter {
  emit(name: string, payload: TodoEventTypes[name]["payload"]): boolean;
  on(name: string, listener: (payload: TodoEventTypes[name]["payload"]) => any): this;
  // ...etc
}

... though the original proposal won't allow this.

@mhegazy mhegazy closed this as completed Feb 20, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

4 participants