core: [Vue compat] @click not working on a component

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

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 22 (7 by maintainers)

Commits related to this issue

Most upvoted comments

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

You need to use the .native modifier.

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.

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

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