-
Notifications
You must be signed in to change notification settings - Fork 546
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
Dynamic event names in emits option #204
Comments
Transferred to RFCs for discussion. |
Can you motivate this RFC with more use cases? I never had a component with dynamic events in my projects, it would help me think about the patterns if a real use case was given. A point to consider: isn't a potential benefit of |
<DynamicFilters :filters="{ foo: [], bar: [], baz: [] }" @update:foo="onUpdateFoo" /> This can not be solved with a generic There's a workaround with an <DynamicFilters :filters="filters" @update:foo="onUpdateFoo">
<div>
<!-- actual component... -->
<!-- will not receive class or attrs -->
</div>
</DynamicFilters> I have a library that emits these kind of events if you're interested: https://github.com/CyberAP/vue-component-media-queries |
Thanks I understand better now, it's an interesting usage. I'm not totally convinced by those 2 examples that the use-cases justify the extra complexity added to Vue core.
I think you could totally fire an event Instead of filtering in the event name, which is a disguised parameter; you'll have to filter in the handler code, or in the template: One may even argue that for a generic component it might be better because if I'm interested in a change in any filter (e.g. to refresh a list), I can do that by listening to just one event. Another design could be to not provide this event at all and rely on the consumer passing a reactive object to |
With the increasing complexity on the developer's side, yes. But in this case we're solving the consequences of attribute inheritance, while we could completely avoid going into that in the first place. Take for example an abstract <StateWatcher @update="$event.prop !== 'foo.bar.baz' && handleEvent($event)" /> This will have very poor performance overall. Compare that to a dynamic listener example: <StateWatcher @update:foo:bar:baz="handleEvent" />
This is unrelated since props and events are partially related. The actual data may be stored somewhere else. |
Very slightly increased complexity on developer's side, but there's complexity on the Vue side as well that needs to be factored in. Your solution to the <StateWatcher watches="foo.bar.baz" @update="handleEvent" /> I'm gonna say it's even more flexible as you could easily add and remove watchers dynamically if that component supports an array: <script> let fields = reactive(['foo', 'bar']); </script>
<StateWatcher :watches="fields" @update="handleEvent" /> |
I would argue that it is not more flexible, but quite the opposite. If we want to watch multiple sources: <StateWatcher :watches="fields" @update="handleEvent" /> const fields = reactive(['foo', 'bar'])
const handleEvent = (event) => {
if (event.field === 'foo') { handleFooEvent(event) }
else if (event.field === 'bar') { handleBarEvent(event) }
} We have created a new reactive property and an event handler just to deal with two events. I think it's a cumbersome way to deal with dynamic events. With dynamic event handlers we get this: <StateWatcher
@update:foo="handleFooEvent"
@update:bar="handleBarEvent"
/> No extra properties or event handlers required. It is a much more concise API no matter how you look at it. The whole idea of this change is to give developers more ways to express their components API. And this actually works for Vue 2, so I don't see why it shouldn't work for Vue 3 since it is a perfectly valid case. |
Doesn't have to be reactive, I think this is perfectly fine: <StateWatcher :fields="['foo', 'bar']" @update="handleChange" />
<script>
function handleChange(e) {
switch (e.field) {
case 'foo':
// do A
break;
case 'bar':
// do B
break;
}
}
</script> And of course you can go fancy if you have many handlers: const handlers = {
foo(e) { },
bar(e) { },
};
function handleChange(e) {
handlers[e.field]?.(e)
}
You missed my point. Say I want to dynamically add or remove a listener for properties. How are you gonna do that? This is what I meant when I said the other solution is more flexible.
Sure, I'm just playing the devil's advocate here. Adding this requires more code in Vue, more documentation, creates more knowledge for users, doesn't mesh well with a potential autocomplete for templates in IDE and will need to remain supported for the foreseeable future... I'm not opposed to the idea, just trying to see if the use cases are many or compelling enough to justify the addition. |
That would be easy to achieve with a <StateWatcher v-on="{ 'update:foo': onFooUpdate, 'update:bar': onBarUpdate }" />
I wouldn't say this feature is required to have IDE support. It is required to filter out dynamic event listeners from attribute fallthrough and that's basically it. We don't have dynamic slot props IDE support and the feature itself is there nonetheless. I would like to know how much maintenance burden it would actually introduce for Vue maintainers. From my perspective it doesn't really seem that hard to maintain if we ignore IDE support, but I might be wrong of course. |
Right! So used to using the shortcut
Hopefully one day Vetur (or one of the new alternative plugins) will support event completion in IDE. If this happens, dynamic events as proposed here won't be supported (I don't see how). That's unfortunate and will feel like a limitation. 😞 Another issue today is Typescript support: when using TS Now, all your examples are not completely random event names, but rather hiding a parameter inside the event name, such as We can come up by many ways to do that, but one simple idea is to consider events in a special way if their name ends with |
If we use an Having a separate rule for |
I meant having some kind of API to define any event prefix, such as if you declare |
I think adding a special case of a If the goal is to have both versatility and at the same time leave const emits = ['foo', '^bar:']
// matches `foo`, `bar:anything` Vue 2 has a similar story with event modifiers for render functions (that didn't last in Vue 3 though). |
Any updates? |
I use the following approach quite often in my components such as navigation items, modal actions, data-table actions, even data-table row items to dynamically provide list of possible actions for that component.. Is this an anti-pattern? What would you suggest I do? (to prevent warnings and be able to declare emits that I do not know in advance) // ActionItems.vue
<div>
<my-button
v-for="(action, i) in actions"
:key="i"
:variant="action.variant"
:icon="action.icon"
@click="$emit(action.event)"
>
{{ action.label }}
</my-button>
</div> actions() {
return [
{
label: 'Create',
event: 'createDocument',
variant: 'button',
icon: 'create',
},
{
label: 'Send',
event: 'sendDocument',
variant: 'button-ghost',
icon: 'send',
},
];
} <action-items
:actions="actions"
@create-document="createDoc"
@send-document="sendDoc"
/> |
Maybe for this case you can use And after import mounted () {
const emits = this.actions.map(action => action.event);
defineEmits(emits)
} |
Awesome! I had no idea this was possible, this should do it. Thank you! |
How is this used? Usually when we define emits, we do this into a variable that can be called when component emits an event, I can't seem to find that here. Thanks |
What problem does this feature solve?
Components might emit events that can not be statically analyzed. For example
v-model
already does this with theupdate:[name || 'modelValue']
. Wherename
is av-model
argument. A custom event could look like this for example:change:[name]
wherename
is derived from a component's prop.What does the proposed API look like?
I think
emits
should support these dynamic event names via guard callbacks:This could also work for event validations:
The text was updated successfully, but these errors were encountered: