Svelte: Why use createEventDispatcher?

Created on 27 Mar 2019  Â·  9Comments  Â·  Source: sveltejs/svelte

I'm currently going through the v3 tutorial and I'm a little bit confused by the use of createEventDispatcher() to emit events:

import { createEventDispatcher } from 'svelte';

const dispatch = createEventDispatcher();

dispatch('event')

From reading the Svelte source, I'm aware that calling this function returns a dispatcher bound to that very component.

However, this feels weirdly verbose for how brief Svelte usually is. Is there a reason we can't (or shouldn't) just do this...

import { dispatch } from 'svelte';

dispatch('event')

...and let the compiler do the actual binding (e.g. directly after importing dispatch to the component)?

Most helpful comment

@JohnnyFun I had the same question today, and have come up with what I hope is an answer. Hopefully someone can correct me if I am not quite right or missing something important.

By using createEventDispatcher, you are telling the compiler to compile your component in such a way that any events it emits adheres to the typical CustomEvent interface. This isn't that big of a deal if you are just dealing with Svelte, but I gather that if you are compiling Svelte components to be used elsewhere as web components, your compiled component will be compiled with an interface other frameworks will understand (i.e. it will have a correctly implemented addEventListener and removeEventListener methods).

If you aren't doing anything tricky like that, then the main benefit I see is easily letting the event of a child component be exposed from a parent. In your example, if you wanted to wrap Thing1 in a parent component Thing1Parent, that parent component can look like this:

<script>
</script>

<Thing1 on:click />

Now, Thing1Parent also has a click event that you can register to, which is fired when the Child is fired. If you used straight methods, it would look like this, which is a slight bit more verbose:

<script>
   export let onClick;
</script>

<Thing1 onClick={onClick}/>

Also, since these are DOM events, you can also use modifiers, such as:

<Thing1 on:click|once={doSomething}/>

From what I can tell, you can get away with passing methods through props. There are some small benefits to using the dispatcher, but it would be pretty much mandatory if you are doing something where your svelte components are used in a system besides Svelte.

All 9 comments

I'm actually not sure - I was about to answer with the standard answer for several questions related to this (that runtime imports from 'svelte' are regular functions, and not treated specially by the compiler), but this doesn't seem to explain it in this case. The current implementation of createEventDispatcher is

function createEventDispatcher() {
    const component = current_component;

    return (type, detail) => {
        const callbacks = component.$$.callbacks[type];

        if (callbacks) {
            // TODO are there situations where events could be dispatched
            // in a server (non-DOM) environment?
            const event = custom_event(type, detail);
            callbacks.slice().forEach(fn => {
                fn.call(component, event);
            });
        }
    };
}

But it feels like we should be able to just directly use current_component right away without the extra layer here.

Maybe we should be — I'm not aware how current_component works exactly.

EDIT: Please ignore the following, I thought the implementation had callbacks, but it's actually all the same call stack.


Obsolete considerations inside

It looks like it's bound to component to avoid things like a suddenly different current_component in callbacks (e.g. setTimeout).

What I mean is this (note the code comments):

// If we replaced all `component` with `current_component`...
function dispatch(type, detail) {
    // ...are we guaranteed to have the same `current_component` here...
    const callbacks = current_component.$$.callbacks[type];

    if (callbacks) {
        const event = custom_event(type, detail);
        callbacks.slice().forEach(fn => {
            // ...and here?
            fn.call(current_component, event);
        });
    }
};

This looks like it would be an issue.

However, regarding the "standard answer": Is there an FAQ where this is written down, maybe with the reasoning why Svelte does touch a lot of stuff, but not this? 🙂

Exports in 'svelte' are regular functions so that you can import them in other non-component files and encapsulate reusable bits of logic that can then be imported in other components and just work. Explanations of these types of decisions probably do belong somewhere on the site, but there's not a good home for them yet.

Alright, that sounds reasonable.

And I think my first considerations were actually correct:

import { dispatch } from 'svelte';

function sayHello() {
    // `current_component` might have changed by the time this is called
    dispatch('message', {
        text: 'Hello!'
    });
}

It still feels a little bit off though to use rather verbose constructs like createEventDistpatcher in the otherwise very terse Svelte code. 😕

However, this definitely clarified my question, thanks for taking care. 🙂

Yeah, you're right, it doesn't work. current_component will likely not still have the correct value by the time you call dispatch(), I'm not sure what I was thinking.

As for the terseness: It's now a lot easier to have props that are callbacks in Svelte 3, so you'll probably be using events less anyway. Having to create a dispatcher is a little verbose, yeah, but it does seem necessary given other goals and constraints.

Related: when would you recommend using createEventDispatcher over simply passing a function? For example, https://svelte.dev/repl/897e056bca05458ab6a4835991d0a788?version=3.16.4 (one component dispatches an event and the calling code retrieves data from event.detail, while the other component invokes a delegate if it's present).

@JohnnyFun I had the same question today, and have come up with what I hope is an answer. Hopefully someone can correct me if I am not quite right or missing something important.

By using createEventDispatcher, you are telling the compiler to compile your component in such a way that any events it emits adheres to the typical CustomEvent interface. This isn't that big of a deal if you are just dealing with Svelte, but I gather that if you are compiling Svelte components to be used elsewhere as web components, your compiled component will be compiled with an interface other frameworks will understand (i.e. it will have a correctly implemented addEventListener and removeEventListener methods).

If you aren't doing anything tricky like that, then the main benefit I see is easily letting the event of a child component be exposed from a parent. In your example, if you wanted to wrap Thing1 in a parent component Thing1Parent, that parent component can look like this:

<script>
</script>

<Thing1 on:click />

Now, Thing1Parent also has a click event that you can register to, which is fired when the Child is fired. If you used straight methods, it would look like this, which is a slight bit more verbose:

<script>
   export let onClick;
</script>

<Thing1 onClick={onClick}/>

Also, since these are DOM events, you can also use modifiers, such as:

<Thing1 on:click|once={doSomething}/>

From what I can tell, you can get away with passing methods through props. There are some small benefits to using the dispatcher, but it would be pretty much mandatory if you are doing something where your svelte components are used in a system besides Svelte.

Sure, I could see that being the explanation. Sounds like there are ways to pass function refs to web component instances, but I suppose people would also want to have a more common interface for their web components' events.

Fwiw, you could do <Thing1 {onClick} /> to make it _almost_ as concise as <Thing1 on:click /> in Thing1Parent.

Also fwiw, I think once might be the only modifier that can be used with customly-dispatched events. I ran into that the other day, when I had a Btn component effectively forwarding the underlying button click event--I needed to stopPropagation of the underlying event. I just opted to use a button element directly instead of my Btn svelte component since I didn't need any of its special functionality in that particular case.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sskyy picture sskyy  Â·  3Comments

Rich-Harris picture Rich-Harris  Â·  3Comments

clitetailor picture clitetailor  Â·  3Comments

matt3224 picture matt3224  Â·  3Comments

rob-balfre picture rob-balfre  Â·  3Comments