简体   繁体   中英

VUE 2+ accessing child methods

I have a situation where in VUE I need to access a child function from within the child as well as from the parent. I have a working example, but there must be a better way to access the child function from the parent.

The example has a couple of event listeners that should expand when clicked or keypressed. I want to use the child.do_something method from the parent with the global EventListener .

Is there a better way to use the child than this?

 // CHILD is a component that needs access to its own click method var child = { props: ['item'], data: function() { return { isActive: false } }, template: ` <div class="key" :class="{playing: isActive}" v-on:click='do_something'> <kbd class="noselect">{{ item.kbd }}</kbd> </div> `, methods: { do_something: function(event) { this.isActive = !this.isActive // DO OTHER STUFF too }, }, } //PARENT also need to access do_something for the right object when a key is pressed var parent = new Vue({ el: '#parent', data: { keysList: [{ keyCode: "65", kbd: "A" }, { keyCode: "83", kbd: "S" }, ], }, components: { 'child': child, }, methods: { keystroke: function(keyCode) { //FIND THE CHILD AND EXECUTE...THIS IS THE TERRIBLE PART const child = this.$children.find(child => { return child.$vnode.data.key === keyCode.toString() }); child.do_something() } }, created: function() { window.addEventListener('keydown', (e) => this.keystroke(e.keyCode)); } }) 
 .keys { display: flex; flex: 1; min-height: 100vh; align-items: center; justify-content: center; } .key { margin: 1rem; transition: all .07s ease; color: white; background: rgba(0, 0, 0, 0.77); } .playing { transform: scale(1.1); border-color: #ffc600; box-shadow: 0 0 1rem #ffc600; } kbd { display: block; font-size: 4rem; } 
 <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <div class="keys" id='parent'> <child v-for="(item,index) in keysList" :item="item" :key="item.keyCode"></child> </div> 

You could use a ref and then reference the particular child you need by index. This would allow you to avoid using the internal values.

<child v-for="(item,index) in keysList" :item="item" :key="item.keyCode" ref="children"></child>

And in keystroke :

const index = this.keysList.findIndex(k => k.keyCode == evt.keyCode)
this.$refs.children[index].do_something()

Here your code modified.

 // CHILD is a component that needs access to its own click method var child = { props: ['item'], data: function() { return { isActive: false } }, template: ` <div class="key" :class="{playing: isActive}" v-on:click='do_something'> <kbd class="noselect">{{ item.kbd }}</kbd> </div> `, methods: { do_something: function(event) { this.isActive = !this.isActive // DO OTHER STUFF too }, }, } //PARENT also need to access do_something for the right object when a key is pressed var parent = new Vue({ el: '#parent', data: { keysList: [{ keyCode: "65", kbd: "A" }, { keyCode: "83", kbd: "S" }, ], }, components: { 'child': child, }, methods: { keystroke: function(evt) { const index = this.keysList.findIndex(k => k.keyCode == evt.keyCode) this.$refs.children[index].do_something() } }, created: function() { window.addEventListener('keydown', this.keystroke); } }) 
 .keys { display: flex; flex: 1; min-height: 100vh; align-items: center; justify-content: center; } .key { margin: 1rem; transition: all .07s ease; color: white; background: rgba(0, 0, 0, 0.77); } .playing { transform: scale(1.1); border-color: #ffc600; box-shadow: 0 0 1rem #ffc600; } kbd { display: block; font-size: 4rem; } 
 <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <div class="keys" id='parent'> <child v-for="(item,index) in keysList" :item="item" :key="item.keyCode" ref="children"></child> </div> 

Or a slightly more direct approach using a different ref for each child.

<child v-for="(item,index) in keysList" :item="item" :key="item.keyCode" :ref="item.keyCode"></child>

And in keystroke :

this.$refs[evt.keyCode][0].do_something()

The unfortunate part here is that because the ref is set as part of a loop, each ref is an array of one element. If I think of a way around that I'll edit it in.

And here is that working.

 // CHILD is a component that needs access to its own click method var child = { props: ['item'], data: function() { return { isActive: false } }, template: ` <div class="key" :class="{playing: isActive}" v-on:click='do_something'> <kbd class="noselect">{{ item.kbd }}</kbd> </div> `, methods: { do_something: function(event) { this.isActive = !this.isActive // DO OTHER STUFF too }, }, } //PARENT also need to access do_something for the right object when a key is pressed var parent = new Vue({ el: '#parent', data: { keysList: [{ keyCode: "65", kbd: "A" }, { keyCode: "83", kbd: "S" }, ], }, components: { 'child': child, }, methods: { keystroke: function(evt) { this.$refs[evt.keyCode][0].do_something() } }, created: function() { window.addEventListener('keydown', this.keystroke); }, }) 
 .keys { display: flex; flex: 1; min-height: 100vh; align-items: center; justify-content: center; } .key { margin: 1rem; transition: all .07s ease; color: white; background: rgba(0, 0, 0, 0.77); } .playing { transform: scale(1.1); border-color: #ffc600; box-shadow: 0 0 1rem #ffc600; } kbd { display: block; font-size: 4rem; } 
 <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <div class="keys" id='parent'> <child v-for="(item,index) in keysList" :item="item" :key="item.keyCode" :ref="item.keyCode"></child> </div> 

You can achieve this by adding one more property active to the items in your parent data keysList & update the active property for the matching keystroke . As you are passing the item as prop to your child, you can use the item.active from the props. You don't need the isActive data in your child.

Parent:

    var parent = new Vue({
  el: '#parent',
  data: {
    keysList: [{
        keyCode: "65",
        kbd: "A",
        active: false
      },
      {
        keyCode: "83",
        kbd: "S",
        active: false
      }
    ],
  },
  components: {
    'child': child,
  },
  methods: {
    keystroke: function(keyCode) {
      this.keysList.forEach(key => {key.active = key.keyCode === keyCode.toString()});
    }
  },
  created: function() {
    window.addEventListener('keydown', (e) => this.keystroke(e.keyCode));
  }
})

<div class="keys" id='parent'>
  <child v-for="(item,index) in keysList" :item="item" :key="item.keyCode" :isActive="item.active"></child>
</div>

Child:

    var child = {
  props: ['item'],

  template: `
    <div class="key" :class="{playing: item.active}" v-on:click='do_something'>
      <kbd class="noselect">{{ item.kbd }}</kbd>
    </div>
  `,

  methods: {
    do_something: function(event) {
       // DO OTHER STUFF too
    },
  },
}

This way you are managing the state just at one place and you don't need to access your child from your parent

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM