Vue v-on:click does not work on component

前端 未结 7 1393
日久生厌
日久生厌 2020-11-27 11:16

I\'m trying to use the on click directive inside a component but it does not seem to work. When I click the component nothings happens when I should get a \'test clicked\' i

相关标签:
7条回答
  • 2020-11-27 11:33

    Native events of components aren't directly accessible from parent elements. Instead you should try v-on:click.native="testFunction", or you can emit an event from Test component as well. Like v-on:click="$emit('click')".

    0 讨论(0)
  • 2020-11-27 11:38

    As mentioned by Chris Fritz (Vue.js Core Team Emeriti) in VueCONF US 2019

    if we had Kia enter .native and then the root element of the base input changed from an input to a label suddenly this component is broken and it's not obvious and in fact, you might not even catch it right away unless you have a really good test. Instead by avoiding the use of the .native modifier which I currently consider an anti-pattern will be removed in Vue 3 you'll be able to explicitly define that the parent might care about which element listeners are added to...

    With Vue 2

    Using $listeners:

    So, if you are using Vue 2 a better option to resolve this issue would be to use a fully transparent wrapper logic. For this Vue provides a $listeners property containing an object of listeners being used on the component. For example:

    {
      focus: function (event) { /* ... */ }
      input: function (value) { /* ... */ },
    }
    

    and then we just need to add v-on="$listeners" to the test component like:

    Test.vue (child component)

    <template>
      <div v-on="$listeners">
        click here
      </div>
    </template>
    

    Now the <test> component is a fully transparent wrapper, meaning it can be used exactly like a normal <div> element: all the listeners will work, without the .native modifier.

    Demo:

    Vue.component('test', {
      template: `
        <div class="child" v-on="$listeners">
          Click here
        </div>`
    })
    
    new Vue({
      el: "#myApp",
      data: {},
      methods: {
        testFunction: function(event) {
          console.log('test clicked')
        }
      }
    })
    div.child{border:5px dotted orange; padding:20px;}
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
    <div id="myApp">
      <test @click="testFunction"></test>
    </div>

    Using $emit method:

    We can also use $emit method for this purpose, which helps us to listen to child components events in parent component. For this, we first need to emit a custom event from child component like:

    Test.vue (child component)

    <test @click="$emit('my-event')"></test>
    

    Important: Always use kebab-case for event names. For more information and demo regading this point please check out this answer: VueJS passing computed value from component to parent.

    Now, we just need to listen to this emitted custom event in parent component like:

    App.vue

    <test @my-event="testFunction"></test>
    

    So, basically instead of v-on:click or the shorthand @click we will simply use v-on:my-event or just @my-event.

    Demo:

    Vue.component('test', {
      template: `
        <div class="child" @click="$emit('my-event')">
          Click here
        </div>`
    })
    
    new Vue({
      el: "#myApp",
      data: {},
      methods: {
        testFunction: function(event) {
          console.log('test clicked')
        }
      }
    })
    div.child{border:5px dotted orange; padding:20px;}
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
    <div id="myApp">
      <test @my-event="testFunction"></test>
    </div>


    With Vue 3

    Using v-bind="$attrs":

    Vue 3 is going to make our life much easier in many ways. One of the examples for it is that it will help us to create a simpler transparent wrapper with very less config by just using v-bind="$attrs". By using this on child components not only our listener will work directly from the parent but also any other attribute will also work just like it a normal <div> only.

    So, with respect to this question, we will not need to update anything in Vue 3 and your code will still work fine as <div> is the root element here and it will automatically listen to all child events.

    Demo #1:

    const { createApp } = Vue;
    
    const Test = {
      template: `
        <div class="child">
          Click here
        </div>`
    };
    
    const App = {
      components: { Test },
      setup() {
        const testFunction = event => {
          console.log("test clicked");
        };
        return { testFunction };
      }
    };
    
    createApp(App).mount("#myApp");
    div.child{border:5px dotted orange; padding:20px;}
    <script src="//unpkg.com/vue@next"></script>
    <div id="myApp">
      <test v-on:click="testFunction"></test>
    </div>

    But for complex components with nested elements where we need to apply attributes and events to main <input /> instead of the parent label we can simply use v-bind="$attrs"

    Demo #2:

    const { createApp } = Vue;
    
    const BaseInput = {
      props: ['label', 'value'],
      template: `
        <label>
          {{ label }}
          <input v-bind="$attrs">
        </label>`
    };
    
    const App = {
      components: { BaseInput },
      setup() {
        const search = event => {
          console.clear();
          console.log("Searching...", event.target.value);
        };
        return { search };
      }
    };
    
    createApp(App).mount("#myApp");
    input{padding:8px;}
    <script src="//unpkg.com/vue@next"></script>
    <div id="myApp">
      <base-input 
        label="Search: "
        placeholder="Search"
        @keyup="search">
      </base-input><br/>
    </div>

    0 讨论(0)
  • 2020-11-27 11:43

    If you want to listen to a native event on the root element of a component, you have to use the .native modifier for v-on, like following:

    <template>
      <div id="app">
        <test v-on:click.native="testFunction"></test>
      </div>
    </template>
    

    or in shorthand, as suggested in comment, you can as well do:

    <template>
      <div id="app">
        <test @click.native="testFunction"></test>
      </div>
    </template>
    
    0 讨论(0)
  • 2020-11-27 11:53

    A bit verbose but this is how I do it:

    @click="$emit('click', $event)"

    UPDATE: Example added by @sparkyspider

    <div-container @click="doSomething"></div-container>
    

    In div-container component...

    <template>
      <div @click="$emit('click', $event);">The inner div</div>
    </template>
    
    0 讨论(0)
  • 2020-11-27 11:53

    From the documentation:

    Due to limitations in JavaScript, Vue cannot detect the following changes to an array:

    1. When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
    2. When you modify the length of the array, e.g. vm.items.length = newLength

    In my case i stumbled on this problem when migrating from Angular to VUE. Fix was quite easy, but really difficult to find:

    setValue(index) {
        Vue.set(this.arr, index, !this.arr[index]);
        this.$forceUpdate(); // Needed to force view rerendering
    }
    
    0 讨论(0)
  • 2020-11-27 11:56

    I think the $emit function works better for what I think you're asking for. It keeps your component separated from the Vue instance so that it is reusable in many contexts.

    // Child component
    <template>
      <div id="app">
        <test @click="$emit('test-click')"></test>
      </div>
    </template>
    

    Use it in HTML

    // Parent component
    <test @test-click="testFunction">
    
    0 讨论(0)
提交回复
热议问题