Skip to content
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

Mapped function types #33881

Closed
5 tasks done
Rziedreo145 opened this issue Oct 8, 2019 · 2 comments
Closed
5 tasks done

Mapped function types #33881

Rziedreo145 opened this issue Oct 8, 2019 · 2 comments

Comments

@Rziedreo145
Copy link

Rziedreo145 commented Oct 8, 2019

Search Terms

mapped function types

Suggestion

Extend the mapped type concept and syntax to allow mapping to a function type.

Use Cases

I am working on a project utilizing vuex. That library has good type definitions, but its store object exposes a very loosely typed API, out of necessity. Some of the most commonly used functions look like this:

interface Dispatch {
  (type: string, payload?: any, options?: DispatchOptions): Promise<any>;
}
interface Commit {
  (type: string, payload?: any, options?: CommitOptions): void;
}

The result is that interactions with the store have essentially no type checking. Since the store typically contains much of the core functionality of an app, it's unfortunate to lack type checking on all arguments to, and returns from that store.

I want to wrap and expose those APIs as more type-constrained functions that only accept known action & mutation names, and the correct payload type and return type associated with each. The ultimate goal is to define a single type representing each collection of actions or mutations, and from there, map to object types that can be used to type check the vuex module that is implementing those actions and mutations, and also map to function types that can be used to wrap vuex's dispatch and commit functions.

More generally, I think this same concept could be applied to many general-purpose tools that expose loosely typed APIs.

To the best of my understanding, the current mapped types syntax only supports mapping to object types.

Examples

interface Person {
  name: string;
  age: number;
}

Starting with an object type like this:

interface PersonActions {
  setName: (name: string) => Person;
  setAge: (age: number) => Person;
}

...I want to use type mapping to map to a function type with overloads corresponding to each action in PersonActions, which would be equivalent to this:

type DispatchPersonAction = {
  (action: 'setName', name: string): Person;
  (action: 'setAge', age: number): Person;
}

To the best of my understanding, the mapped type syntax only supports mapping to object types.

type DispatchAction<T> = {
  [P in keyof T]: T[P] extends (...args: any) => any
    ? (action: P, ...args: Parameters<T[P]>) => ReturnType<T[P]>
    : never
};

Using the DispatchAction mapped type above with the PersonActions interface results in an object type with members setName and setAge:

type DispatchPersonAction = DispatchAction<PersonActions>;
// Equivalent to:
type DispatchPersonAction = {
  setName: (action: 'setName', name: string) => Person;
  setAge: (action: 'setAge', age: number) => Person;
}

Syntactically, I think this would require conceiving of an alternative way of defining a mapped type such that the P in keyof T portion doesn't result in a member name on the mapped type. I don't have an informed suggestion for this alternative syntax, but am hoping someone out there does!

Thanks!

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@ark120202
Copy link

That's actually already possible, because intersection of function types is effectively the same as overloads and there's a UnionToIntersection hack (see #29594).

Playground

@Rziedreo145
Copy link
Author

Wow @ark120202, thanks for the super quick response including a proof of concept with my examples! I just plugged it back into my project and it works like a charm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants