diff --git a/src/component.js b/src/component.js index 9fb5178bc5..a06fb79a49 100644 --- a/src/component.js +++ b/src/component.js @@ -215,24 +215,34 @@ export function enqueueRender(c) { * @param {import('./internal').Component} a * @param {import('./internal').Component} b */ -const depthSort = (a, b) => a._vnode._depth - b._vnode._depth; +const depthSort = (a, b) => b._vnode._depth - a._vnode._depth; /** Flush the render queue by rerendering all queued components */ function process() { - let c; - rerenderQueue.sort(depthSort); + let c, + l = 1; + // Don't update `renderCount` yet. Keep its value non-zero to prevent unnecessary // process() calls from getting scheduled while `queue` is still being consumed. - while ((c = rerenderQueue.shift())) { + while (rerenderQueue.length) { + // Keep the rerender queue sorted by (depth, insertion order). The queue + // will initially be sorted on the first iteration if it has 2 or more items. + // + // New items can be added to the queue e.g. when rerendering a provider, so we want to + // keep the order from top to bottom with those new items so we can handle them in a + // single pass + // + // The `l` variable tracks max(1, previous known sorted size) to know when to + // re-sort, but also to skip sorting when queue length < 2. + if (rerenderQueue.length > l) { + rerenderQueue.reverse().sort(depthSort); + } + + c = rerenderQueue.pop(); + l = rerenderQueue.length || 1; + if (c._dirty) { - let renderQueueLength = rerenderQueue.length; renderComponent(c); - if (rerenderQueue.length > renderQueueLength) { - // When i.e. rerendering a provider additional new items can be injected, we want to - // keep the order from top to bottom with those new items so we can handle them in a - // single pass - rerenderQueue.sort(depthSort); - } } } process._rerenderCount = 0;