Vue封装useModal/useSlideover时如何实现多实例状态独立隔离
问题根因
你贴出的composable基础逻辑本身不存在全局共享问题——每次调用函数时都会在内部新建独立的ref,正常使用时多实例状态应该完全隔离。出现父子模态框联动关闭,基本是以下几个常见错误导致的:
- 实际运行代码中,
isOpen被定义在了useModal/useSlideover函数外部,成为模块级全局单例状态,所有调用composable的组件共享同一个响应式变量,修改时会同步影响所有实例 - 模态框/滑出层的通用关闭逻辑(ESC键关闭、点击蒙层关闭)做了全局绑定,触发时没有做实例判断,直接调用了所有已生成实例的close方法
- 子组件没有独立调用composable生成私有状态,而是通过props、inject等方式拿到了父组件的模态框状态,操作的实际是父组件的
isOpen - 多层模态框同时存在时,点击上层蒙层/内容的事件冒泡到了下层父模态框的蒙层,触发了父模态框的关闭逻辑;或是多个模态框错误绑定了同一个v-model控制变量
修复方案
1. 保证状态实例级隔离
严格将响应式状态定义在composable函数内部,不要提升到函数外层。可以抽离通用逻辑减少重复代码,同时给每个实例生成唯一标识做区分,改造后代码如下:
import { ref } from 'vue' // 抽离遮罩层通用状态逻辑 const createOverlayComposable = () => { const instanceId = Symbol('overlay') const isOpen = ref(false) const open = () => { isOpen.value = true } const close = () => { isOpen.value = false } const toggle = () => { isOpen.value = !isOpen.value } return { instanceId, isOpen, open, close, toggle } } export const useSlideover = () => createOverlayComposable() export const useModal = () => createOverlayComposable()
2. 修复全局关闭逻辑的误触发
如果需要支持ESC关闭、点击蒙层关闭的通用能力,不要直接全局绑定事件触发所有实例关闭,维护一个打开实例的栈结构做管理:
- 新打开的模态框/滑出层实例入栈,关闭时出栈
- 触发关闭事件时,只操作栈顶(最上层)的实例,避免影响下层遮罩
参考实现:
import { ref } from 'vue' // 模块内维护已打开遮罩的实例栈 const openOverlayStack = [] const createOverlayComposable = () => { const instanceId = Symbol('overlay') const isOpen = ref(false) const open = () => { isOpen.value = true if (!openOverlayStack.includes(instanceId)) { openOverlayStack.push(instanceId) } } const close = () => { // 非最上层实例不响应全局关闭事件 if (openOverlayStack.at(-1) !== instanceId) return isOpen.value = false const idx = openOverlayStack.indexOf(instanceId) if (idx > -1) openOverlayStack.splice(idx, 1) } const toggle = () => isOpen.value ? close() : open() return { instanceId, isOpen, open, close, toggle } } // 全局只绑定一次ESC监听 if (typeof window !== 'undefined') { window.addEventListener('keydown', (e) => { if (e.key === 'Escape' && openOverlayStack.length) { e.stopPropagation() // 如果栈存的是实例引用而非ID,可直接调用栈顶实例的close方法 } }) } export const useSlideover = () => createOverlayComposable() export const useModal = () => createOverlayComposable()
3. 排查组件使用问题
- 每个独立模态框、滑出层必须在自身的
<script setup>作用域内单独调用对应composable生成私有状态,不要直接把父组件解构出的open/close方法传给子组件控制独立模态框 - 模态框用Teleport挂载到全局时,给蒙层和内容容器加
@click.stop阻止事件冒泡,避免点击上层模态框时触发下层父模态框的蒙层点击关闭逻辑 - 检查v-model绑定,不要让多个模态框的显示隐藏绑定到同一个响应式变量
内容的提问来源于stack exchange,提问作者SPQRInc
相关产品推荐
相关产品推荐

