如何为手动挂载的Vue组件注册卸载回调以移除DOM容器?
问题描述
我实现了一个可在TypeScript函数中挂载组件(无需在<template>标签中定义)的composable,具体代码如下:
export const useMountDialog = () => { const appContext = inject<AppContext>('$useMountDialog') if (!appContext) { throw new Error( 'Could not find app context. Make sure to provide the app instance.' ) } const mountDialog = <T extends { new (...args: any[]): any }>( component: T, props: ExtractPropTypes<InstanceType<T>> ) => { const container = document.createElement('div') const vNode = h(component, props) vNode.appContext = appContext render(vNode, container) const unmount = () => { console.log('Unmount') // document.body.removeChild(container); render(null, container) } document.body.appendChild(container) } const mountDialogAndWait = async <T extends { new (...args: any[]): any }>( component: T, props: ExtractPropTypes<InstanceType<T>>, data?: any ) => { const confirmDialogReturn = useConfirmDialog() const extendedProps = { ...props, confirmDialogReturn } mountDialog(component, extendedProps) return await confirmDialogReturn.reveal(data) } return { mountDialog, mountDialogAndWait } }
目前调用unmount方法仅能卸载组件,但容器div并未从DOM中移除。我曾尝试使用vNode.props?.onVnodeUnmounted,但vnode并未触发卸载,仅组件本身被卸载。请问该如何为组件注册卸载事件,以实现移除容器div的需求?
解决方案
方案一:通过vnode生命周期钩子直接监听卸载
你之前尝试的onVnodeUnmounted没生效,是因为需要将这个钩子作为props传入vnode,而不是直接访问vNode.props。修改mountDialog方法,在创建vnode时添加该钩子:
const mountDialog = <T extends { new (...args: any[]): any }>( component: T, props: ExtractPropTypes<InstanceType<T>> ) => { const container = document.createElement('div') // 将onVnodeUnmounted加入props const vNode = h(component, { ...props, onVnodeUnmounted: () => { if (container.parentNode) { container.parentNode.removeChild(container) } } }) vNode.appContext = appContext render(vNode, container) const unmount = () => { console.log('Unmount') render(null, container) } document.body.appendChild(container) // 返回unmount方法供外部调用 return { unmount } }
当调用unmount()执行render(null, container)后,组件的vnode会触发onVnodeUnmounted钩子,此时即可安全移除容器节点。
方案二:调用unmount时直接移除容器
render(null, container)已经完成了组件的卸载清理,此时直接移除容器是完全安全的。只需修改unmount方法:
const unmount = () => { console.log('Unmount') render(null, container) // 组件卸载后立即移除容器 if (container.parentNode) { container.parentNode.removeChild(container) } }
同时记得把unmount方法返回出去,让外部能正确触发这个逻辑:
// 在mountDialog末尾添加返回 return { unmount }
方案三:组件内部通过钩子通知外部移除容器
如果需要组件自身控制卸载时机,可以给组件添加自定义回调props,在组件内部的onUnmounted钩子中触发:
- 修改
mountDialog方法:
const mountDialog = <T extends { new (...args: any[]): any }>( component: T, props: ExtractPropTypes<InstanceType<T>> ) => { const container = document.createElement('div') const extendedProps = { ...props, // 添加自定义卸载回调 onDialogUnmounted: () => { if (container.parentNode) { container.parentNode.removeChild(container) } } } const vNode = h(component, extendedProps) vNode.appContext = appContext render(vNode, container) const unmount = () => { console.log('Unmount') render(null, container) } document.body.appendChild(container) return { unmount } }
- 在弹窗组件内部:
<script setup lang="ts"> import { onUnmounted } from 'vue' // 定义接收的回调props const props = defineProps<{ onDialogUnmounted?: () => void }>() onUnmounted(() => { // 组件卸载时触发回调移除容器 props.onDialogUnmounted?.() }) </script>
内容的提问来源于stack exchange,提问作者MrSpt
相关产品推荐
相关产品推荐

