vuejs / core

🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
https://vuejs.org/
MIT License
47.82k stars 8.35k forks source link

Forwarding of emits through intermediate components between parent/grandchild like in Svelte #5917

Open Arturexe opened 2 years ago

Arturexe commented 2 years ago

What problem does this feature solve?

Svelte has this awesome feature, where you forward emits through intermediate components between parent/grandchild.

This feature would help managing emits, since components can get big and confusing, esp. with a lot of props/emits being passed around.

What does the proposed API look like?

GrandChild.vue:

<template>
    <GrandChild @click=click/>
</template>

<script setup>
const emit = defineEmits([
    'emitData'
])

const click = (data) => {
    emit('emitData', data)
}
</script>

Child.vue:

<template>
    <Child @emitData>
</template>

or

<template>
    <Child @emitData=$up>
</template>

or something more creative..., and it would be received by the parent just like it is now:

<template>
    <Parent @emitData=getData/>
</template>

<script setup>
const getData = (data) => {
    console.log(data)
}
</script>
MellKam commented 1 year ago

I don't know for sure, but it seems like toHandlers is what you need.

UPD: No, it doesnt work.

Here's a utility from shadcn-vue https://github.com/radix-vue/shadcn-vue/blob/ae81084f367b704615f6e6677c91cbae466c4978/apps/www/src/lib/utils.ts#L17-L34 that I've improved a bit and it works great for me.

import { camelize, getCurrentInstance, toHandlerKey } from "vue";

function useEmitAsProps<EventName extends string>(
    emit: (name: EventName, ...args: any[]) => void
) {
    const result: Record<string, any> = {};
    const vm = getCurrentInstance();
    if (!vm) return result;

    const events: EventName[] = Array.isArray(vm.type.emits)
        ? vm.type.emits
        : typeof vm.type.emits === "object"
        ? Object.keys(vm.type.emits)
        : [];

    if (!events.length) {
        console.warn(
            `No emitted event found. Please check component: ${vm.type.__name}`
        );
    }

    for (let i = 0; i < events.length; i++) {
        result[toHandlerKey(camelize(events[i]!))] = (...args: any) =>
            emit(events[i]!, ...args);
    }

    return result;
}
gianj988 commented 2 months ago

Hi

if you need to propagate custom events through components, i wrote and published (on npm) an Event Bus that does precisely that. It's installable like any other Vue3 plugin and it has also stopPropagation and once options. The documentation is on the readme and explains all that you need. If you have questions, problems or ideas to add, you can write on the issue section of the package gitHub page. The link to the NPM page is: https://www.npmjs.com/package/vue-events-backbone

Note that, since custom events propagation is something that has been removed from Vue Developers, I did not extend the vue emits mechanism. I built a parallel independent mechanism. Let me know if it was helpful.

nmhungdev commented 1 month ago

Based on @MellKam guide. Check out my example to see how it works.

gianj988 commented 1 month ago

Based on @MellKam guide. Check out my example to see how it works.

Hi, from what I read in your example, you want something to forward events emitted from a child through middle components until they reach a parent that listens for them. Am I correct? I needed something like that too, but i didn't want to have to "forward" custom events. That is why I started writing that package. Its a plugin that allows you to define special emitters on your child component and register handlers on its parents components. In this way you don't have to forward anything in your middle components because the job of propagating events is already being done by my plugin internally. By default my plugin propagates events through the components tree, just like DOM-events bubble up through the DOM-tree. Plus it allows you to control the event propagation with "stopPropagation", "once" and "transformEvent" functions. If you need more information check the documentation readme on the NPM page I linked in my previous post.