Vue3通过插槽调用子组件方法问题(Vue2迁移)
Vue3 插槽中子组件方法调用问题(Vue2迁移场景)
问题场景
从Vue2迁移至Vue3时,原Vue2中通过this.$children访问子组件并调用方法的方式无法使用。在Form组件中尝试遍历默认插槽获取FormSubmit子组件、调用其lock方法时,出现child.lock is not a function报错;尝试通过VNode的component属性获取实例时,componentInstance始终为null。
核心原因
- Vue3已废弃
$children属性,插槽返回的是VNode对象而非组件实例,直接调用VNode的方法必然报错。 - 直接调用
this.slots.default()会生成新的VNode副本,而非页面上已挂载的组件对应的VNode,因此其component属性为null。 - 即使获取到正确的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
相关产品推荐
相关产品推荐

