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

Typed event dispatcher and event handlers #5211

Closed
AlexAegis opened this issue Jul 26, 2020 · 15 comments
Closed

Typed event dispatcher and event handlers #5211

AlexAegis opened this issue Jul 26, 2020 · 15 comments

Comments

@AlexAegis
Copy link

Is your feature request related to a problem? Please describe.
I'd like to have typesafe event handlers, for this the type of the event has to be known beforehand.

Describe the solution you'd like
The createEventDispatcher function being extended with a generic and a parameter. The parameter would restrict the handler to only handle one kind of event, where the detail field's type would be determined by the type of the generic.

The new type signature of createEventDispatcher would look like this:

export declare function createEventDispatcher(): (type: string, detail?: any) => void;
export declare function createEventDispatcher<T>(type: string): (detail?: T) => void;

Example usage

child.svelte

<script lang="ts">
  const fooDispatcher = createEventDispatcher<{ foo: string }>('foo');
  fooDispatcher({ foo: 'hello' });
</script>

app.svelte

<script lang="ts">
  import Child from './child.svelte';

  function fooHandler(foo: { foo: string }) {}
  function barHandler(bar: { bar: string }) {}

</script>
<Child on:foo={fooHandler} />
<Child on:foo={barHandler} /> <!-- Error, type mismatch! -->
<Child on:foo={(foo) => console.log(foo.foo)} /> <!-- Inferred type, intellisense -->

Possible problems

The svelte compiler has to be sure that only one createEventDispatcher have been defined for any given type, to prevent redefing the dispatcher with a different type. (This is easier if eventDispatchers are only allowed to be created on the top level and as const, but this would be a breaking change)

TypeScript can't protect against something like dispatching a malformed event through a regular eventDispatcher. To solve this either the untyped createEventDispatcher() would need to be deprecated, or the svelte compiler has to forbid the mixed usage of these.

Currently there is no way to export type definitions from components, so they have to be defined elsewhere and import type-d into the component. It's a different topic but would increase usability if the type of events could be imported from the component directly.

How important is this feature to you?
It's important to provide type safety where it's possible.

@dummdidumm
Copy link
Member

dummdidumm commented Aug 12, 2020

I propose a different typing which would only change the typing but keep the implementation the same:

export declare function createEventDispatcher<EventMap extends {} = any>()
   : <EventKey extends Extract<keyof EventMap, string>>(type: EventKey, detail?: EventMap[EventKey]) => void;


const bla = createEventDispatcher();
bla('anythingAllowedSinceNoEventMapTypingGiven', true);

const bla2 = createEventDispatcher<{click: boolean}>();
bla2('qwd', true); // <-- error, "qwd" not assignable to type "click"
bla2('click', ''); // <-- error, string not assignable to type boolean

dummdidumm added a commit to dummdidumm/svelte that referenced this issue Aug 12, 2020

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
Makes it possible to explicitly type which events can be dispatched like so:

```ts
const bla2 = createEventDispatcher<{click: boolean}>();
bla2('click', ''); // error, type string not assignable to type boolean
bla2('qwd', true); // error, "qwd" not assignable to "click"
```

sveltejs#5211
@AlexAegis
Copy link
Author

AlexAegis commented Aug 13, 2020

But does this ensure in other components that when I listen to click I get a boolean? In other components the return type of the click event handlers has to be boolean?

@dummdidumm
Copy link
Member

This is an issue for the language-tools repo, this will not be type-checked if we don't add support for that in the language-tools repo. This also applies to your proposal.

@dummdidumm
Copy link
Member

dummdidumm commented Sep 2, 2020

Closing since the intent of this issue was to get type-safe event-handling. Dispatching in a type safe way within the component was added with the enhanced typings. Getting IDE support for typed event dispatchers on other components will be work to do in the language-tools.

@Conduitry
Copy link
Member

Svelte 3.25.0 now has createEventDispatcher with the types @dummdidumm mentioned above.

@AlexAegis
Copy link
Author

AlexAegis commented Sep 11, 2020

Could you reopen this? This is not what I described in this issue. The issue's title states "Typed event dispatcher and event handlers", not just event dispatchers

These new typings only restrict the events that have to be dispatched.

Take a look at what I described in the Example usage section of the issue, the important part is that on the recieving end I know what type of event can come out so the event handler is correct. Right now on 3.25.0 it's still CustomEvent<any>. In the example I denoted that barHandler in that case should cause an error, that is not happening right now.

Although I like the idea that you can define the types of the events per event in one type, I think my suggestion is needed for of the second part and thats why this issue is opened in this repository. The reason being that even if I were to bring the type of the event handler myself to the recieving end I can't extract correct types from this. Unless I define it explicitly, but inference is preferred.

	const eh = createEventDispatcher<{ hello: { world: number }, foo: {bar: string} }>();
	eh('hello', { world: 12 });
	eh('foo', { bar: '12' });

	function handle(a: Parameters<typeof eh>[1]) {
		//a: {
		//	world: number;
		//} | {
		//	bar: string;
		//} | undefined
	}

Even if I could define multiple event dispatchers (Technically I think I can) that defeats the purpose of being able to define the types of multiple events at once.

@dummdidumm
Copy link
Member

dummdidumm commented Sep 11, 2020

I stated above that this is something that needs to be handled in language tools. We will add support for it soon. That work can be tracked here sveltejs/language-tools#424 .

@AlexAegis
Copy link
Author

I think it can work out with this kind of event dispatcher generics if it's possible in typescript to union the generics of all event dispatchers defined (In the case of svelte)

@doom-goober
Copy link

doom-goober commented Jun 12, 2021

Here are some more details for people searching how to use the new createEventDispatcher:

  • Event with no details:
    createEventDispatcher<{click:void}>()

  • Consuming events with typed details:
    createEventDispatcher<{click:number}>()
    on:click={(event:CompleteEvent)=> { console.log(event.detail) } } //event.detail is typed to number here.

@opensas
Copy link
Contributor

opensas commented Feb 1, 2022

@doom-goober I just asked a question about it at so

What is the correct way to type an event with no detail? event: null or event:void?
thanks a lot

@doom-goober
Copy link

@opensas I also answered on SO but I gave a poor answer there. The TL;DR is that I would do eventName:null OR eventName:{}. That's because both null and {} (empty object} are both of type object. I know in my example I did eventName:void (I don't remember where I got that example from.) But in the end, remember that the generic only matters at compile time to TypeScript and the generic will be stripped out by the compiled JavaScript, so as long as it compiles and gives the right warnings, it doesn't really matter. But the rational choice would be null or {} (empty object) because they have type consistency.

@opensas
Copy link
Contributor

opensas commented Feb 2, 2022

@doom-goober thanks a lot for your reply. I'll just go with createEventDispatcher<{discard: null}>() because it aligns better with the spec. See what MDN has to say about it:

"detail", optional and defaulting to null, of any type, containing an event-dependent value associated with the event. This is available to the handler using the CustomEvent.detail property.

@ivanhofer
Copy link
Contributor

I have created a PR that improves the type-definitions of createEventDispatcher.

@opensas thanks for the link to the MDN docs. I first assued the default value was undefined

@opensas
Copy link
Contributor

opensas commented Feb 7, 2022

I hope it gets merged, and also that we can find it in the docs. Svelte documentation is great, but sometimes is not so easy to find how to type components and events.

@ivanhofer
Copy link
Contributor

I also had this Problem a few times. I'm currently working on a repo where all the TypeScript features of Svelte and SvelteKit are shown.

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

No branches or pull requests

7 participants