List View: Speed up opening by removing a second render pass - #80935
Conversation
|
Size Change: +36 B (0%) Total Size: 7.78 MB 📦 View Changed
|
|
Flaky tests detected in eb12240. 🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/30704554054
|
71463fb to
2ea2f43
Compare
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
| lastWindow.start <= firstViewableIndex && | ||
| lastWindow.end >= firstViewableIndex + visibleItems | ||
| ) { | ||
| return lastWindow; |
There was a problem hiding this comment.
Maybe we should avoid leaving fixedListWindow.visibleItems stale here? Right now, consumers continue receiving the previous value of visibleItems until a scroll or resize occurs.
There was a problem hiding this comment.
That should be okay, because I don't think we can keep this value interactive and retain perf fix, which should be a fine trade-off IMO.
Honestly, I'm not sure why the hook returns this value; there's not much consumers can do with it. Event List View only used the itemInView.
P.S. I think we should start looking into adopting a battle-tested virtualization library rather than maintaining our own. But that's something for the future.
There was a problem hiding this comment.
I explored @tanstack/react-virtual a little bit. It might be hard to adapt to our exact use case in the list view. And anyway, we have only a single use case for this right now.
There was a problem hiding this comment.
The idea of the patch is: at the moment we're deciding whether to update the nextWindow or not, we already did most of the work of rendering the window content. So, if the nextWindow is a strict subset of lastWindow, there's no point bothering to rerender a smaller window. We should rerender if some items are missing.
But this can happen not only on first measurement, but also any time later! Can we expand the optimization so that it never makes the window strictly smaller, but only expands it or slides it up/down?
2ea2f43 to
9f48af3
Compare
|
Some ideas for further refactoring: Both effects should have Debouncing with The hook doesn't need to return the The two effects should be organized differently. The first one only measures the window when there is a reason to measure it: useLayoutEffect( () => {
measureWindow();
}, [ reasons, to, remeasure ] );Reasons include change of The second effect should only attach and remove listeners. Its only dependency should be There is no reason why a change in |
9f48af3 to
eb12240
Compare
|
Thanks for the feedback, @jsnajdr! I started refactoring and had Claude running perf metrics against the baseline in the background. The results are a bit different than what you suggested.
|
| fixedListWindow.visibleItems, | ||
| useWindowing, | ||
| options?.expandedState, | ||
| elementRef, |
There was a problem hiding this comment.
The elementRef is only here to satisfy ESLint.
That's fair, the ideas probably need some adjustment to make them good 🙂 Thank for the existing improvement, let's merge. |
|
I love finding this kind of PRs :) The results are clear on the perf graph. |
What?
Builds on top of #80929.
Makes
useFixedWindowListskip re-rendering its initial measurement triggers when the already-rendered window covers the visible items.Only applies while the default
initWindowSizeof 30 covers the viewport(panels up to ~1080px); taller panels keep the old two-pass path.
Why?
Opening List View committed twice inside one click: 31 placeholder rows render, a layout effect measures the scroll container, then
setFixedListWindowforces a second synchronous render before paint. Each commit re-dirties the style, and each one incurs a full forced style recalculation; the recalc cost is per-pass overhead, roughly independent of how many rows the commit inserts, so the second pass was ~as expensive as the first for 5 extra rows.How?
On the initial measurement, keep the current window if it already spans
[firstViewableIndex, firstViewableIndex + visibleItems]. The pre-measurement window (30 items) is normally a superset of the measured window, so nothing needs to be re-rendered. Later measurements (scroll/resize) update as before, so the window still shrinks while scrolling.visibleItemsmoves to a ref, so measuring no longer re-renders on its own and Page Up/Down pages by the measured viewport rather than the initial guess.Only applies while the default
initWindowSizeof 30 covers the viewport (panels up to ~1080px); taller panels keep the old two-pass path.Testing Instructions
Testing Instructions for Keyboard
Same.
Screenshots or screencast
The last two Perf checks on run CI for
listViewOpenmetric.Use of AI Tools
Assisted by Claude.