Subscribe on changes!

[Vue compat] @click not working on a component

avatar
Sep 12th 2021

Version

3.2.11

Reproduction link

codesandbox.io

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

avatar
Sep 12th 2021

You need to use the .native modifier.

avatar
Sep 12th 2021

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. image

avatar
Sep 12th 2021

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

avatar
Sep 12th 2021

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

avatar
Sep 12th 2021

Yes, as my title says, im using the vue 3 compat build which is the migration build.

avatar
Sep 12th 2021

Could you reopen the issue please as this seems like a bug. I've been stuck with this issue all day :(

avatar
Sep 13th 2021

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:

  1. The component instance no longer provides this.$listeners (Vue 2 API, not present in Vue 3)
  2. 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

  1. Keep .native in templates until the children are ready to handle native events in the Vue 3 way
  2. First, migrate any children that use $listeners to use $attrs instead (usually used in combination with inheritAttrs: false)
  3. document emitted events in child components with emits: []
  4. In each of these child components, disable INSTANCE_LISTENERS compat behavior
  5. Now remove all usage of .native

/cc @vuejs/docs

avatar
Sep 23rd 2021

Is it just the problem in codesandbox.io? It performs correctly in SFC Playground

avatar
Sep 23rd 2021

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. image

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.

https://v3.vuejs.org/guide/events.html

avatar
Sep 23rd 2021

You need to use the .native modifier.

He is using vue3 not vue2.

avatar
Sep 23rd 2021

I fixed my issue by adding INSTANCE_LISTENERS: false as suggested 😀

avatar
Sep 23rd 2021

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.

https://v3.vuejs.org/guide/installation.html#vite

avatar
Sep 23rd 2021

Vite has nothing to do with this question.

avatar
Sep 23rd 2021

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.

https://v3.vuejs.org/guide/installation.html#vite

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.

avatar
Jan 26th 2022

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.

avatar
Jan 26th 2022

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?

avatar
Jan 26th 2022

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.

avatar
Jan 26th 2022

Thanks, that's something to work with 👍

avatar
Mar 3rd 2023

@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?

avatar
Mar 3rd 2023

Yes. So to leave them in Vue 3 more you would have to add that config to those components. Can be done at runtime.

avatar
Mar 3rd 2023

@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?

avatar
Jun 22nd 2023

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" />