基于vue3.0和element-plus的组件库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

3.1 KiB

Virtual Scrolling Debug Notes

Date: 2026-04-03

Issue: Virtual Scrolling Not Updating on Scroll

Symptoms

  • Rows weren't shifting when scrolling programmatically
  • Debug info showed correct scrollTop but rows stayed the same
  • Force re-render (classList toggle) fixed it momentarily

Root Causes Found

1. Vue Template Ref Auto-Unwrapping

Problem: virtualizer.visibleRows is a ComputedRef inside an object. Vue 3 auto-unwraps top-level refs in templates, but NOT refs nested inside objects.

Evidence:

TypeScript error: Property 'index' does not exist on type 'true | ComputedRefImpl<any> | VirtualRow[]'

Fix: Destructure the needed values from virtualizer:

const { visibleRows, totalHeight: virtualTotalHeight, onScroll } = virtualizer;

2. Vue 3 Template .value Usage

Problem: In Vue 3 <script setup>, refs are auto-unwrapped in templates. Using .value explicitly on refs returned from a hook (inside an object) causes type inference issues.

Before (broken):

<div v-for="row in virtualizer.visibleRows.value">

After (working):

<div v-for="row in visibleRows">

3. Overscan + Small Dataset

Problem: With 10 rows (each ~60px = 600px total) and viewport 306px, overscan=5 means all 10 rows are rendered regardless of scroll position.

Calculation:

  • Viewport shows ~5 rows
  • Overscan 5 = render rows -5 to +5 from viewport edge
  • 5 + 5 + visible rows = all 10 rows always visible

Not a bug - virtual scrolling only benefits when rows > viewport + overscan.

Key Code Changes

list-table-v2.vue

// Before: all refs nested in object
const virtualizer = useVirtualRows(...);

// After: destructure for template auto-unwrap
const { visibleRows, totalHeight: virtualTotalHeight, onScroll } = virtualizer;

useVirtualRows.ts

// Added safety guard for invalid indices
const visibleRows = computed<VirtualRow[]>(() => {
  if (startIndex < 0 || endIndex < startIndex) {
    return [];
  }
  // ... rest of computation
});

Debug Techniques Used

  1. Force re-render: table.classList.add('x'); requestAnimationFrame(() => table.classList.remove('x'))

    • If rows change after force-re-render, reactivity IS working but scroll event isn't triggering updates
  2. Check virtualizer state: Debug info showed Visible Rows: 10 and Total Rows: 10 - all rows were intentionally rendered

  3. Calculate expected behavior:

    • viewport = 306px
    • total content = 660px (10 rows)
    • max scroll = 354px
    • overscan = 5 on each side

Lessons Learned

  1. In Vue 3 <script setup>, always destructure refs that need to be used in templates
  2. Virtual scrolling benefit is only visible when total_rows > viewport + (overscan * row_height)
  3. Vue 3 auto-unwraps top-level refs but NOT refs nested in objects
  4. console.log in Vue event handlers doesn't appear in browser console - Vue intercepts events
  5. Programmatic scroll (scrollTop = x) doesn't fire scroll events - need to manually dispatch

Files Modified

  • packages/base/data/list-table-v2/list-table-v2.vue
  • packages/base/data/list-table-v2/useVirtualRows.ts