[Vue compat] @click not working on a component
Version
3.2.11
Reproduction link
Steps to reproduce
Clicking on "click mee"
What is expected?
Should log "click" in the console
What is actually happening?
Not doing anything
I tried adding COMPILER_V_ON_NATIVE: false
to vue.config.js but it seems to make no difference. This issue only happens in the compat build of vue
Oh, but in the documents, it says to remove all .natives? confused https://v3.vuejs.org/guide/migration/v-on-native-modifier-removed.html#_2-x-syntax I also get this huge error in my real project to actually remove .native.
Also even with the .native modifier, it still doesnt work with the compat build of vue 3.
https://codesandbox.io/s/sharp-proskuriakova-woc9s?file=/src/App.vue
Are you using the migration build? The compiler flag you mentioned can only work with the migration build. See https://v3.vuejs.org/guide/migration/migration-build.html#migration-build
Could you reopen the issue please as this seems like a bug. I've been stuck with this issue all day :(
So I've been looking into this, and it doesn't really qualify as a bug. Rather, it's a complicated interaction between multiple breaking changes and their corresponding COMPAT flags/behaviors. This might need a special entry in the migration docs.
Short summary
- in Vue 2, the parent is responsible for applying native root node listeners, in Vue 3, it's the child.
- When COMPILER_V_ON_NATIVE is enabled and the parent used
.native
, we do emulate this by passing a flag along the event to the child component which can then properly handle the "native" event. - When COMPILER_V_ON_NATIVE is disabled and the parent doesn't use
.native
anymore, we pass the event like a normal component event listener to the child (no additional flag). - The child, running in Vue 2 compat mode, does treat all incoming listeners as component event listeners and doesn't add anything to the root node.
Solution
In the child component, the following compatConfig
has to be set:
export default {
compatConfig: {
INSTANCE_LISTENERS: false,
}
}
This has the following effects:
- The component instance no longer provides
this.$listeners
(Vue 2 API, not present in Vue 3) - All incoming event listeners that are not declared in the component's
emits: []
option are added as native listeners to the template root element. (new Vue 3 behavior)
If the child is already fully migrated and can run in Vue 3 mode, the problem would also be solved:
export default {
compatConfig: {
MODE: 3,
}
}
Concerning Migration documentation
So this is one of the most complex migration steps in fact, as it involves a change in behavior in the parent and child interaction.
- In Vue 2, the parent was responsible to take care of native listeners marked with
.native
. - In Vue 3, the child does take care of them by treating all non-declared events as native.
- This touches on multiple changes:
.native
removal$listeners
removal and non-declared events now being part of$attrs
- Requirement to explicitly declare a component's emitted events with
emits
So what would be a good migration strategy that we can document? I think this would be good
- Keep
.native
in templates until the children are ready to handle native events in the Vue 3 way - First, migrate any children that use
$listeners
to use$attrs
instead (usually used in combination withinheritAttrs: false
) - document emitted events in child components with
emits: []
- In each of these child components, disable
INSTANCE_LISTENERS
compat behavior - Now remove all usage of
.native
/cc @vuejs/docs
Is it just the problem in codesandbox.io? It performs correctly in SFC Playground
Oh, but in the documents, it says to remove all .natives? confused https://v3.vuejs.org/guide/migration/v-on-native-modifier-removed.html#_2-x-syntax I also get this huge error in my real project to actually remove .native.
I learn vue3 directly I don't even know this .native
I don't think you have to add it.
@click="someFunc"
is commonly used in vue3
and this is exactly the offical example for event handling.
I fixed my issue by adding INSTANCE_LISTENERS: false as suggested
I recommend you to use vite2 to build your vue3 apps. Vite2 don't need to config these options and everything works the same way as official docs.
I fixed my issue by adding INSTANCE_LISTENERS: false as suggested
I recommend you to use vite2 to build your vue3 apps. Vite2 don't need to config these options and everything works the same way as official docs.
I'm using the migration build of vue 3 which makes it easier to migrate a project from vue 2 to 3. Thats why i have to use these options.
Thank you @LinusBorg! I have been trying to figure out why this wasn't working. The only thing I will add to the above solution is some more details around where to put INSTANCE_LISTENERS
. I attempted to add this to compilerOptions
in vue.config.js
prior to finding this issue and it did not seem to work at all. It would seem this is a component specific compat behavior.
But it already says it has to be set "in the child component".
And on top, the migration guide explains that all Compat flags that need to be set in the compiler Options are prefixed with COMPILER_*
I'm not sure how to make it more clear, do you have a concrete suggestion?
And on top, the migration guide explains that all Compat flags that need to be set in the compiler Options are prefixed with COMPILER_*
Yes, however, it is not explicit that only the flags that are prefixed with COMPILER_
will work in the build setup and not the other flags that are not prefixed with it. This is what is confusing.
I'm not sure how to make it more clear, do you have a concrete suggestion?
On the Compat Configuration section, it defines 4 different places you add these flags. It doesn't offer any real explanation as to the purpose behind which one should be used, why and when it would make sense to use one over the other (as in this issue's case).
Instead, the guide comes across as "provide these flags where ever you want". So, when I follow the installation in the guide for vue-cli
(which already has compatConfig
in the compilerOptions
) and my immediate thought is: "Oh! I can just put any compat flags here."
Which is what I did and why I couldn't get it to work prior to finding this issue. That is what I meant by "adding more details around where to put INSTANCE_LISTENERS
":
Adding a simple and general disclaimer on the guide that there might be times where you may need to add these flags specifically to components to get them to work properly.
@LinusBorg Stupid question, but if I have a project where I set compatConfig globally to Mode: 2 and then import a vanilla vue 3 component library (external npm dependency), does this force those components to run in Mode: 2 if they don't specifically declare a compatConfig with Mode: 3 themselves?
Yes. So to leave them in Vue 3 more you would have to add that config to those components. Can be done at runtime.
@LinusBorg
Thanks. Like this? If so, I think this behaviour and workaround should be documented, will try to open a PR with the docs project.
<script lang="ts">
import { ExternalVue3Component } from "@acme-corp/ui-toolkit";
(ExternalVue3Component as any).compatConfig = { MODE: 3 };
//...
</script>
Background to my question: BootstrapVue, which is a fairly prevalent and widely used Vue library, requires setting MODE: 2
globally with their compat build - they don't have a native Vue 3.0 package yet. This in turn leads to downstream issues if you include external libraries written in Vue 3.0, which is kinda what you do when upgrading a project from Vue 2.0 to Vue 3.0.
I would have assumed that Vue somehow would prevent the described behavior when encountering external components written in Vue 3.0 - what does it even mean to force a component written with the composition API to conform to MODE: 2?
Thanks for the clear explanations Linus.
We had the issue while migrating to ElementPlus through the compat build, and we fixed it like this:
// main.ts
import { ElForm } from 'element-plus';
ElForm.compatConfig = {
...ElForm.compatConfig,
INSTANCE_LISTENERS: false,
};
Then we can still prevent unwanted form submissions this way:
<el-form @submit.prevent.stop="handleSubmit" />