forked from mengyxu/noob-components
3 changed files with 128 additions and 2 deletions
@ -0,0 +1,95 @@
@@ -0,0 +1,95 @@
|
||||
# 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`: |
||||
```typescript |
||||
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)**: |
||||
```vue |
||||
<div v-for="row in virtualizer.visibleRows.value"> |
||||
``` |
||||
|
||||
**After (working)**: |
||||
```vue |
||||
<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 |
||||
```typescript |
||||
// Before: all refs nested in object |
||||
const virtualizer = useVirtualRows(...); |
||||
|
||||
// After: destructure for template auto-unwrap |
||||
const { visibleRows, totalHeight: virtualTotalHeight, onScroll } = virtualizer; |
||||
``` |
||||
|
||||
#### useVirtualRows.ts |
||||
```typescript |
||||
// 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` |
||||
Loading…
Reference in new issue