Modal模态框开发避坑指南与性能优化实践

萌新.柚溪 组件 阅读 1,526
赞 25 收藏
二维码
手机扫码查看
反馈

项目初期的技术选型

最近刚结束了一个后台管理系统,客户要求实现一个多功能的Modal模态框组件。说实话,当时觉得这需求挺常规的,不就是弹个框嘛。但实际做下来才发现,这里面门道不少。

Modal模态框开发避坑指南与性能优化实践

考虑到项目使用的是Vue3技术栈,我决定基于Composition API来封装这个Modal组件。一方面是为了代码复用性高,另一方面也是想练练手。毕竟现在前端框架更新这么快,多掌握些新特性总是好的。

最大的坑:性能问题

开始写的时候还挺顺的,基本功能很快就实现了。但当我在页面中同时打开多个Modal时,发现页面明显卡顿了。通过Chrome DevTools分析,发现问题出在每次打开Modal都会重新渲染整个组件树。

折腾了半天才发现,原来是我把所有逻辑都写在setup函数里,导致每次打开都要重新执行一遍。后来调整了方案,把一些静态配置提取到外部,只保留必要的响应式数据在组件内部。

// 优化后的Modal组件核心代码
import { ref, watchEffect } from 'vue'

export default {
  props: {
    visible: Boolean,
    content: String
  },
  setup(props) {
    const isVisible = ref(props.visible)
    
    watchEffect(() => {
      isVisible.value = props.visible
    })
    
    const close = () => {
      isVisible.value = false
    }
    
    return { isVisible, close }
  }
}

又踩坑了,touchmove滚动失效

你以为这就完了?更头疼的问题还在后面。当Modal打开时,页面背景还是会随着用户的手势滑动。这在手机上体验特别差,客户也反馈说这问题必须解决。

最开始我想简单粗暴地用overflow:hidden来禁止背景滚动,但这会导致页面抖动,用户体验更差了。最后采用了比较取巧的方法,在body上加了个固定定位:

/* Modal样式处理 */
.modal-open {
  position: fixed;
  width: 100%;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  overflow: hidden;
}
// 在组件中控制body样式
watch(isVisible, (newVal) => {
  if (newVal) {
    document.body.classList.add('modal-open')
  } else {
    document.body.classList.remove('modal-open')
  }
})

数据交互的意外状况

项目中还遇到了一个很奇葩的问题。Modal里的表单需要调用接口获取初始化数据,但有时会因为网络延迟导致数据显示异常。开始没想到会有这种特殊情况,结果测试环境一切正常,上线后却频频报错。

后来加了个loading状态控制,并且对接口返回做了更严格的校验。虽然改完后还是有极小概率会出现数据不同步的情况,但至少不会影响主流程了。

const fetchData = async () => {
  try {
    loading.value = true
    const res = await fetch('https://jztheme.com/api/data')
    if (res.status === 200) {
      data.value = await res.json()
    }
  } catch (error) {
    console.error(error)
  } finally {
    loading.value = false
  }
}

最终的解决方案

经过几轮优化,现在的Modal组件已经能满足大部分场景了。支持动态内容注入、异步加载、多种尺寸切换等功能。不过还是有些小遗憾,比如动画效果还可以更流畅些,键盘导航的支持也不够完善。

这里分享下完整的核心代码,亲测有效:

<template>
  <div v-if="isVisible" class="modal-overlay">
    <div class="modal-content">
      <button @click="close">关闭</button>
      <slot></slot>
    </div>
  </div>
</template>

<script>
import { ref, watchEffect } from 'vue'

export default {
  props: {
    visible: Boolean
  },
  emits: ['update:visible'],
  setup(props, { emit }) {
    const isVisible = ref(props.visible)
    
    watchEffect(() => {
      isVisible.value = props.visible
    })
    
    const close = () => {
      isVisible.value = false
      emit('update:visible', false)
    }
    
    return { isVisible, close }
  }
}
</script>

<style>
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,0.5);
  display: flex;
  justify-content: center;
  align-items: center;
}

.modal-content {
  background: #fff;
  padding: 20px;
  border-radius: 8px;
  max-width: 90%;
  max-height: 90%;
  overflow: auto;
}
</style>

回顾与反思

总的来说,这个Modal组件的开发过程让我学到了不少东西。从最初的简单封装,到最后考虑各种边界情况,确实花了不少时间。但也正因为这些踩坑经历,让我对组件设计有了更深的理解。

  • 做得好的地方:组件的可复用性和性能都有不错的提升
  • 还能改进的地方:动画效果和无障碍支持可以继续优化
  • 踩坑提醒:一定要重视移动端的特殊场景,很多PC端没问题的功能在手机上可能会出幺蛾子

以上是我在项目中使用Modal模态框的一些实战经验,希望对你有帮助。有更好的实现方式欢迎评论区交流,或者你遇到过什么奇葩问题也可以一起吐槽下。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论