Make exporting empty functions a way to declare component event dispatchers
This is a proposal to introduce a more straightforward way to declare component events, namely through the use of empty functions. Exporting a named function with an empty body would declare an event, and calling that function would dispatch it.
<script lang="ts">
export function message(msg: string) {}
message("I just dispatched a message event!");
</script>
The consumer remains the same
<Component on:message={console.log} /> // "I just dispatched a message event!"
This solution comes as a hybrid between prop functions and createEventDispatcher :
export function message(){} |
export let onMessage = noop |
createEventDispatcher | |
|---|---|---|---|
on: syntax |
✅ | ❌it's a prop | ✅ |
| Event forwarding | ✅ | ❌it's a prop | ✅ |
| Amount of listeners | ✅ Infinity | ❌1 | ✅Infinity |
| Setup | ✅ one line | ✅ one line | ❌>3 lines |
| "Is there events ?" | ✅ it's in the exports | ✅ it's in the exports | ❌could be anywhere |
| Arguments | ✅ like any function | ✅ like any function | ❌1 argument ❌wrapped in a CustomEvent |
| Listener callback | ✅ like any function | ✅ like any function | ❌must destructure CustomEvent ❌must fill type manually |
| Typings | ✅ like any function | ✅ like any function | ❌troublesome |
| Refactoring | ✅ like any function | ✅ like any function | ❌troublesome |
As a side effect this proposal also introduces the ability to rename events when forwarding them:
<script lang="ts">
export function hoverLeft(event: MouseEvent) {}
export function hoverRight(event: MouseEvent) {}
</script>
<button on:mouseover={hoverLeft} />
<button on:mouseover={hoverRight} />
The implementation is simple, the compiler just has to fill the body of those functions with createEventDispatcher's :
function message(...args) {
const listeners = $$self.$$.callbacks.message;
if (listeners) listeners.slice().forEach((fn) => fn.apply($$self, args))
}
I cannot think of any realistic scenario where this would be a breaking change.
createEventDispatcher has been criticized multiple times #2323 #3488 #4584 #5211 #5597. I for one actively avoid using it. The go-to solution appears to be to introduce yet another reserved variable #5598, I see all of those $$variables slowly creeping up on svelte and I'm very much not looking forward to see another.
this sounds like a big change, i would suggest open a RFC https://github.com/sveltejs/rfcs/ and have a better discussion over there
I like the idea of dispatching an event without having to deal with it being an actual Event object, but some downsides:
- It's not obvious what it does
- What if you actually just want to export an empty function as a no-op for whatever reason?
- You may get warnings for unused arguments
- With
on:you'll have an event without anEventobject. You might expect to be able to pass the event to a function that handles all events. - It might be confusing to have an event handler take different arguments depending on if it's called from
on:oraddEventListener. - Would not work if you want an argument with the same name as an event
Hi it might be naive of me thinking this but for me a fix for what @probablykasper said would look like this:
<script lang="ts">
export message = createEventDispatcher<string>();//message is of type function ()=> string
message("I just dispatched a message event!");
</script>
This would make it possible to create and export empty functions without messing with the type system. Would there be any problem with this aproach.
I am a little scared that such a feature will not make it into svelte.
An awesome idea! I too came up with something very similar called Auto-dispatch. But it has added default behaviour which I think is very powerful. Svelte is about being svelte after all and createEventDispatcher() is just downright ugly. I think this proposal should be seriously considered. And since it has some minor breaking changes, it could be a part of Svelte 5 which is supposed to be a full rewrite of the Svelte compiler with new features and breaking changes anyway.