You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3通过插槽调用子组件方法问题(Vue2迁移)

Vue3 插槽中子组件方法调用问题(Vue2迁移场景)

问题场景

从Vue2迁移至Vue3时,原Vue2中通过this.$children访问子组件并调用方法的方式无法使用。在Form组件中尝试遍历默认插槽获取FormSubmit子组件、调用其lock方法时,出现child.lock is not a function报错;尝试通过VNode的component属性获取实例时,componentInstance始终为null。

核心原因

  1. Vue3已废弃$children属性,插槽返回的是VNode对象而非组件实例,直接调用VNode的方法必然报错。
  2. 直接调用this.slots.default()会生成新的VNode副本,而非页面上已挂载的组件对应的VNode,因此其component属性为null。
  3. 即使获取到正确的VNode,也需确保组件已完成挂载,component属性才会指向有效实例。

解决方案

以下两种方式均可可靠实现需求:

方式一:通过模板引用(Refs)收集插槽内子组件

在Form组件中遍历插槽内容,为符合条件的子组件绑定ref并收集实例:

<template>
    <form>
        <template v-for="(vnode, index) in $slots.default?.() || []" :key="index">
            <component :is="vnode" :ref="el => collectSubmitRef(el, vnode)"></component>
        </template>
    </form>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-facing-decorator'
import FormSubmit from './FormSubmit.vue'

@Component({
    name: 'Form'
})
export default class Form extends Vue {
    private submitRefs: FormSubmit[] = []

    private collectSubmitRef(el: any, vnode: any) {
        if (vnode.type.name === 'FormSubmit' && el) {
            this.submitRefs.push(el)
        }
    }

    public submit(): void {
        this.submitRefs.forEach(submit => {
            if (typeof submit.lock === 'function') {
                submit.lock()
            }
        })
    }

    public beforeUnmount(): void {
        this.submitRefs = []
    }
}
</script>

方式二:使用Provide/Inject实现跨组件方法调用

通过Form组件提供注册/注销方法,FormSubmit组件注入后注册自身,Form即可直接调用子组件方法:

修改Form.vue

<template>
    <form>
        <slot></slot>
    </form>
</template>

<script lang="ts">
import { Component, Vue, Provide } from 'vue-facing-decorator'
import FormSubmit from './FormSubmit.vue'

@Component({
    name: 'Form'
})
export default class Form extends Vue {
    private submitComponents: FormSubmit[] = []

    @Provide()
    private registerSubmitComponent = (component: FormSubmit) => {
        this.submitComponents.push(component)
    }

    @Provide()
    private unregisterSubmitComponent = (component: FormSubmit) => {
        this.submitComponents = this.submitComponents.filter(c => c !== component)
    }

    public submit(): void {
        this.submitComponents.forEach(component => {
            component.lock()
        })
    }
}
</script>

修改FormSubmit.vue

<template></template>
<script lang="ts">
import { Component, Vue, Inject, OnMounted, OnUnmounted } from 'vue-facing-decorator'

@Component({
    name: 'FormSubmit',
    expose: ['lock']
})
export default class FormSubmit extends Vue {
    @Inject() private registerSubmitComponent!: (component: FormSubmit) => void
    @Inject() private unregisterSubmitComponent!: (component: FormSubmit) => void

    @OnMounted()
    private onMounted() {
        this.registerSubmitComponent(this)
    }

    @OnUnmounted()
    private onUnmounted() {
        this.unregisterSubmitComponent(this)
    }

    public lock(): void {
        console.log('lock the button')
    }
}
</script>

原方案失效说明

直接调用this.slots.default()生成的是全新的VNode副本,并非页面上已完成挂载的组件对应的VNode,因此component属性始终为null。必须操作已渲染的组件实例,而非重新生成的VNode对象。

内容的提问来源于stack exchange,提问作者undefinedman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 08:23:12