核心设计思路

基于Vue和React的Modal组件需要遵循以下设计原则:

  • 状态驱动:通过visible属性控制显示/隐藏
  • 插槽机制:支持内容区域、头部、底部插槽自定义
  • 事件系统:提供onCloseonConfirm等回调
  • 无障碍访问:实现焦点管理、键盘事件监听

Vue 3实现方案

组件模板结构

<template>
  <transition name="fade">
    <div 
      v-show="visible"
      class="modal-mask"
      @click.self="handleMaskClick"
    >
      <div class="modal-container">
        <header v-if="$slots.header">
          <slot name="header"></slot>
        </header>
        
        <div class="modal-body">
          <slot></slot>
        </div>

        <footer v-if="$slots.footer || showDefaultFooter">
          <slot name="footer">
            <button @click="handleCancel">取消</button>
            <button @click="handleConfirm">确认</button>
          </slot>
        </footer>
      </div>
    </div>
  </transition>
</template>

组件逻辑实现

import { watchEffect } from 'vue'

export default {
  props: {
    visible: Boolean,
    closeOnClickMask: {
      type: Boolean,
      default: true
    }
  },
  emits: ['update:visible', 'close', 'confirm'],
  setup(props, { emit }) {
    const handleMaskClick = () => {
      if (props.closeOnClickMask) {
        emit('update:visible', false)
        emit('close')
      }
    }

    const handleCancel = () => {
      emit('update:visible', false)
      emit('close')
    }

    const handleConfirm = () => {
      emit('confirm')
    }

    watchEffect(() => {
      document.body.style.overflow = props.visible ? 'hidden' : ''
    })

    return { handleMaskClick, handleCancel, handleConfirm }
  }
}

React实现方案

函数组件实现

import { useEffect } from 'react'

export default function Modal({
  visible,
  onClose,
  onConfirm,
  closeOnClickMask = true,
  children,
  footer
}) {
  const handleMaskClick = () => {
    if (closeOnClickMask) {
      onClose?.()
    }
  }

  useEffect(() => {
    document.body.style.overflow = visible ? 'hidden' : ''
    return () => {
      document.body.style.overflow = ''
    }
  }, [visible])

  if (!visible) return null

  return (
    <div className="modal-mask" onClick={handleMaskClick}>
      <div className="modal-container" onClick={e => e.stopPropagation()}>
        <div className="modal-body">{children}</div>
        
        {footer ?? (
          <div className="modal-footer">
            <button onClick={onClose}>取消</button>
            <button onClick={onConfirm}>确认</button>
          </div>
        )}
      </div>
    </div>
  )
}

样式关键点

基础样式框架

.modal-mask {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.modal-container {
  background: white;
  border-radius: 4px;
  min-width: 300px;
  max-width: 80%;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}

.fade-enter-active, .fade-leave-active {
  transition: opacity 0.3s;
}
.fade-enter-from, .fade-leave-to {
  opacity: 0;
}

高级功能扩展

动态挂载方案

// Vue示例:通过createApp动态挂载
export function showModal(options) {
  const container = document.createElement('div')
  const app = createApp(Modal, {
    ...options,
    onClose: () => {
      options.onClose?.()
      unmountModal()
    }
  })
  
  const unmountModal = () => {
    app.unmount()
    document.body.removeChild(container)
  }
  
  document.body.appendChild(container)
  app.mount(container)
}

键盘事件处理

// React示例:ESC键关闭
useEffect(() => {
  const handleKeyDown = (e) => {
    if (e.key === 'Escape') {
      onClose?.()
    }
  }
  
  if (visible) {
    document.addEventListener('keydown', handleKeyDown)
  }
  
  return () => {
    document.removeEventListener('keydown', handleKeyDown)
  }
}, [visible, onClose])

性能优化建议

  • 使用CSS will-change属性优化动画性能
  • 对于频繁开关的Modal,采用keep-alive缓存组件状态(Vue)
  • 避免在Modal内部使用大量响应式数据
  • 按需加载Modal内容(如配合Suspense使用)
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐