如何从 vue.js 中的父方法访问子方法

我有两个嵌套组件,从父组件访问子方法的正确方法是什么?

this.$children[0].myMethod()似乎起到了作用,但它相当丑陋,不是吗? 还有什么更好的办法:

<script>
import child from './my-child'


export default {
components: {
child
},
mounted () {
this.$children[0].myMethod()
}
}
</script>
227304 次浏览

VueJS 中的亲子沟通

假设根 Vue 实例可以通过 this.$root被所有后代访问,父组件可以通过 this.$children数组访问子组件,子组件可以通过 this.$parent访问它的父组件,您的第一反应可能是直接访问这些组件。

VueJS 文档特别提出了两个非常好的理由:

  • 它把父母和孩子紧密地联系在一起(反之亦然)
  • 您不能依赖于父级的状态,因为它可以被修改 子组件。

解决方案是使用 Vue 的自定义事件接口

Vue 实现的事件接口允许您向上和向下通信组件树。利用自定义事件接口,您可以访问四个方法:

  1. $on()-允许您在 Vue 实例上声明一个监听器,用于监听事件
  2. $emit()-允许您在同一个实例(self)上触发事件

使用 $on()$emit()的例子:

const events = new Vue({}),
parentComponent = new Vue({
el: '#parent',
ready() {
events.$on('eventGreet', () => {
this.parentMsg = `I heard the greeting event from Child component ${++this.counter} times..`;
});
},
data: {
parentMsg: 'I am listening for an event..',
counter: 0
}
}),
childComponent = new Vue({
el: '#child',
methods: {
greet: function () {
events.$emit('eventGreet');
this.childMsg = `I am firing greeting event ${++this.counter} times..`;
}
},
data: {
childMsg: 'I am getting ready to fire an event.',
counter: 0
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.28/vue.min.js"></script>


<div id="parent">
<h2>Parent Component</h2>
<p>\{\{parentMsg}}</p>
</div>


<div id="child">
<h2>Child Component</h2>
<p>\{\{childMsg}}</p>
<button v-on:click="greet">Greet</button>
</div>

答案采取从原来的职位: VueJS 中组件之间的通信

你可以使用 裁判

import ChildForm from './components/ChildForm'


new Vue({
el: '#app',
data: {
item: {}
},
template: `
<div>
<ChildForm :item="item" ref="form" />
<button type="submit" @click.prevent="submit">Post</button>
</div>
`,
methods: {
submit() {
this.$refs.form.submit()
}
},
components: { ChildForm },
})

如果您不喜欢紧耦合,可以使用 活动巴士,如@Yosvel Quintero 所示。下面是另一个使用事件总线的示例,它将总线作为道具传入。

import ChildForm from './components/ChildForm'


new Vue({
el: '#app',
data: {
item: {},
bus: new Vue(),
},
template: `
<div>
<ChildForm :item="item" :bus="bus" ref="form" />
<button type="submit" @click.prevent="submit">Post</button>
</div>
`,
methods: {
submit() {
this.bus.$emit('submit')
}
},
components: { ChildForm },
})

组件代码。

<template>
...
</template>


<script>
export default {
name: 'NowForm',
props: ['item', 'bus'],
methods: {
submit() {
...
}
},
mounted() {
this.bus.$on('submit', this.submit)
},
}
</script>

Https://code.luasoftware.com/tutorials/vuejs/parent-call-child-component-method/

为了让子组件与另一个子组件通信,我在父组件中创建了一个方法,该方法调用子组件中的一个方法:

this.$refs.childRef.childMethod()

Child Ref 是子组件的引用,可以用子组件中的任何方法替换 child 方法。

从另一个孩子那里,我称之为 root 方法:

this.$root.theParentMethod() // It works with Bootstrap Vue
this.$parent.theParentMethod()

这招对我很管用。

当控件呈现受到 v-if的影响时,Ref 和事件总线都会出现问题。所以我决定用一个更简单的方法。

其思想是使用数组作为队列来发送需要调用到子组件的方法。一旦组件被挂载,它将处理这个队列。它监视队列以执行新方法。

(从 Desmond Lua 的回答中借用了一些代码)

母组件代码:

import ChildComponent from './components/ChildComponent'


new Vue({
el: '#app',
data: {
item: {},
childMethodsQueue: [],
},
template: `
<div>
<ChildComponent :item="item" :methods-queue="childMethodsQueue" />
<button type="submit" @click.prevent="submit">Post</button>
</div>
`,
methods: {
submit() {
this.childMethodsQueue.push({name: ChildComponent.methods.save.name, params: {}})
}
},
components: { ChildComponent },
})

这是 ChildComponent 的代码

<template>
...
</template>


<script>
export default {
name: 'ChildComponent',
props: {
methodsQueue: { type: Array },
},
watch: {
methodsQueue: function () {
this.processMethodsQueue()
},
},
mounted() {
this.processMethodsQueue()
},
methods: {
save() {
console.log("Child saved...")
},
processMethodsQueue() {
if (!this.methodsQueue) return
let len = this.methodsQueue.length
for (let i = 0; i < len; i++) {
let method = this.methodsQueue.shift()
this[method.name](method.params)
}
},
},
}
</script>

而且还有很大的改进空间,比如将 processMethodsQueue移动到搅拌器..。

建议的解决方案是针对 Vue 2的,但是如果您最终在这里寻找 Vue 3组合 API 解决方案,您可以在迁移时执行以下操作:

模板中的子组件,其方法为“ doSomething”:

 <div class="form">
<child-component ref="childComponentRef" />
</div>

Vue 2:

this.$refs.childComponentRef.doSomething( );
       

与 Vue 3组合 Api:

    setup( )
{
const childComponentRef = ref( );


childComponentRef.value.doSomething( )


return {
childComponentRef
}
}

如果你来这里找 Vue 3

对于版本 > = 3.2.0

<!-- Parent -->
<template>
<ChildComponent ref="childComponentRef" />
</template>


<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './components/ChildComponent.vue'


const childComponentRef = ref()


onMounted(() => {
childComponentRef.value.doSomething()
})
</script>

脚本设置

<!-- Child -->
<script setup>
const doSomething = () => {
console.log('Im batman')
}


defineExpose({
doSomething
})
</script>

安装功能

<!-- Child -->
<script>
import { defineComponent } from 'vue'


export default defineComponent({
setup(props, context) {
const doSomething = () => {
console.log('Im batman')
}


context.expose({ doSomething })
}
})
</script>

如果 Vue 版本为 < 3.1.3,则必须使用 setup函数并返回函数以在父组件中访问它。

<!-- Child -->
<script>
import { defineComponent } from 'vue'


export default defineComponent({
setup() {
const doSomething = () => {
console.log('Im batman')
}


return { doSomething }
}
})
</script>

我喜欢 mohghaderi 的回答,但是我遇到了几个问题,所以我将使用他的示例代码来显示我需要做的更改,以使其工作。(在我自己的项目中,我使用 Vue 3和 Options API。)

Mohghaderi 的父组件代码以及关于我的更改的注释:

import ChildComponent from './components/ChildComponent'


new Vue({
el: '#app',
data: {
item: {},
childMethodsQueue: [],
},
// Note: In the template below, I added @child-methods-finished="childMethodsFinished"
//       as an event listener, so that we can reset the childMethodsQueue array to
//       empty once the methods are finished.
//       If you don't reset it, then the methods stay in there and cause problems.
template: `
<div>
<ChildComponent :item="item"
:methods-queue="childMethodsQueue"
@child-methods-finished="childMethodsFinished" />
<button type="submit" @click.prevent="submit">Post</button>
</div>
`,
methods: {
submit() {
this.childMethodsQueue.push({
name: ChildComponent.methods.save.name,
params: {}  // Note: delete the {} and put the name of your params, if you use a method that passes in params.
})
}
},
components: { ChildComponent },
})

Mohghaderi 的子组件代码以及关于我的更改的注释:

import { objectToString } from "@vue/shared"


export default {
name: 'ChildComponent',
props: {
methodsQueue: { type: Array },
},
// Note:  I had to rewrite the watch option because it would not trigger.
//        You have to add "deep, true" for arrays and objects.
//        The function has to be called "handler" for it to work as well.
watch: {
methodsQueue: {
handler() {
this.processMethodsQueue()
},
deep: true,
}
},
// Note:  Remove "mounted()" function if you don't want it to run on the mounted event.
mounted() {
this.processMethodsQueue()
},
methods: {
save() {
console.log("Child saved...")
},
processMethodsQueue() {
if (!this.methodsQueue) return
let len = this.methodsQueue.length


if (!len) return  // Note:  This is required to prevent an infinite loop.
//        When we reset the childMethodsQueue array to empty,
//        it will trigger this method through the watch option,
//        so we need this in order to stop the cycle once we are done.


// Note:  Instead of using ".shift()" to access an item in the array
//        we need to use "[i]" otherwise we will get muliple calls of the method
for (let i = 0; i < len; i++) {
let method = this.methodsQueue[i]
this[method.name](method.params)
}


// Note:  Now that we are done calling methods, we need to emit an event back to the parent
//        so it can call it's method to reset the childMethodsQueue array to empty
this.$emit('child-methods-finished')
},
},
}