You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3中Vue-cal加载大量DOM元素的优化方案咨询

Vue-cal 大量事件渲染卡顿的优化策略

针对Vue3中使用Vue-cal加载数千条事件导致的DOM插入卡顿问题,结合Pinia响应式状态的特性,以下是几种可行的优化方案:

1. 基于视图范围过滤事件(核心优化)

日历组件只需要渲染当前可见视图(日/周/月)内的事件,无需一次性加载所有数据。通过监听Vue-cal的view:change事件,动态从Pinia的全量事件中筛选出当前视图范围内的事件,大幅减少DOM渲染数量。

<template>
  <vue-cal 
    @view:change="handleViewChange" 
    :events="visibleEvents" 
    initial-view="month"
  />
</template>

<script setup>
import { ref } from 'vue'
import { useCalendarStore } from '@/stores/calendar'

const calendarStore = useCalendarStore()
const visibleEvents = ref([])

// 过滤当前视图范围内的事件
const handleViewChange = (view) => {
  const { start, end } = view
  visibleEvents.value = calendarStore.allEvents.filter(event => {
    const eventStart = new Date(event.start)
    const eventEnd = new Date(event.end || event.start)
    // 判断事件是否与当前视图有交集
    return eventStart <= end && eventEnd >= start
  })
}

// 初始化加载当前视图事件
handleViewChange({ 
  start: new Date(new Date().setDate(1)), // 当月第一天
  end: new Date(new Date().setMonth(new Date().getMonth() + 1, 0)) // 当月最后一天
})
</script>

2. 分块更新响应式状态(解决一次性插入卡顿)

直接一次性修改Pinia的响应式数组会触发大量依赖更新,导致主线程阻塞。可以将事件分块,结合nextTick或requestIdleCallback,在浏览器空闲时逐步更新状态:

方案A:结合nextTick分块

// Pinia store 中的action
import { nextTick } from 'vue'

export const useCalendarStore = defineStore('calendar', {
  state: () => ({ events: [] }),
  actions: {
    async bulkAddEvents(rawEvents) {
      const chunkSize = 100
      this.events = [] // 清空现有数据
      for (let i = 0; i < rawEvents.length; i += chunkSize) {
        const chunk = rawEvents.slice(i, i + chunkSize)
        this.events.push(...chunk)
        await nextTick() // 等待当前DOM更新完成后再处理下一块
      }
    }
  }
})

方案B:利用requestIdleCallback

// Pinia store 中的action
export const useCalendarStore = defineStore('calendar', {
  state: () => ({ events: [] }),
  actions: {
    bulkAddEvents(rawEvents) {
      let index = 0
      const chunkSize = 50

      const processChunk = () => {
        const end = Math.min(index + chunkSize, rawEvents.length)
        for (; index < end; index++) {
          this.events.push(rawEvents[index])
        }
        if (index < rawEvents.length) {
          // 浏览器空闲时继续处理下一块
          requestIdleCallback(processChunk)
        }
      }

      requestIdleCallback(processChunk)
    }
  }
})

3. 关闭Vue-cal非必要功能减少渲染开销

Vue-cal默认开启了拖拽、事件弹窗等功能,这些会增加组件的渲染和交互开销。根据业务需求关闭不需要的功能:

<vue-cal
  :events="events"
  disable-drag-and-drop
  disable-event-popup
  disable-context-menu
  :max-events-per-day="6" // 限制单日显示的事件数量,超出部分折叠
/>

4. 使用v-memo缓存事件节点

Vue3的v-memo指令可以缓存DOM节点,只有当依赖的属性变化时才重新渲染。通过自定义Vue-cal的事件插槽,给每个事件节点添加缓存:

<vue-cal :events="events">
  <template #event="{ event }">
    <!-- 仅当事件的id、开始时间、结束时间变化时,才重新渲染该节点 -->
    <div v-memo="[event.id, event.start, event.end]" class="custom-event">
      <span class="event-title">{{ event.title }}</span>
      <span class="event-time">{{ event.start }}</span>
    </div>
  </template>
</vue-cal>

5. 跳过响应式转换减少监听开销

如果事件数据不需要响应式更新(比如仅用于展示),可以使用markRaw跳过Vue的响应式转换,避免不必要的依赖追踪:

// Pinia store 中的action
import { markRaw } from 'vue'

export const useCalendarStore = defineStore('calendar', {
  state: () => ({ events: [] }),
  actions: {
    loadStaticEvents(rawEvents) {
      // 标记为非响应式,减少性能开销
      this.events = markRaw(rawEvents)
    }
  }
})

注意:如果后续需要修改事件数据,此方法不适用,需确保数据是静态只读的。

内容的提问来源于stack exchange,提问作者arthur646

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 15:30:08