diff --git a/packages/runtime-core/__tests__/component.spec.ts b/packages/runtime-core/__tests__/component.spec.ts index b9483931e..566be0b82 100644 --- a/packages/runtime-core/__tests__/component.spec.ts +++ b/packages/runtime-core/__tests__/component.spec.ts @@ -1,3 +1,5 @@ +import { h, ref, render, nodeOps, nextTick } from '@vue/runtime-test' + describe('renderer: component', () => { test.todo('should work') @@ -7,5 +9,47 @@ describe('renderer: component', () => { test.todo('componentProps') - test.todo('componentSlots') + describe('slots', () => { + test('should respect $stable flag', async () => { + const flag1 = ref(1) + const flag2 = ref(2) + const spy = jest.fn() + + const Child = () => { + spy() + return 'child' + } + + const App = { + setup() { + return () => [ + flag1.value, + h( + Child, + { n: flag2.value }, + { + foo: () => 'foo', + $stable: true + } + ) + ] + } + } + + render(h(App), nodeOps.createElement('div')) + expect(spy).toHaveBeenCalledTimes(1) + + // parent re-render, props didn't change, slots are stasble + // -> child should not update + flag1.value++ + await nextTick() + expect(spy).toHaveBeenCalledTimes(1) + + // parent re-render, props changed + // -> child should update + flag2.value++ + await nextTick() + expect(spy).toHaveBeenCalledTimes(2) + }) + }) }) diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts index 9510d3cb6..dbf68c879 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -138,7 +138,9 @@ export function shouldUpdateComponent( // this path is only taken by manually written render functions // so presence of any children leads to a forced update if (prevChildren != null || nextChildren != null) { - return true + if (nextChildren == null || !(nextChildren as any).$stable) { + return true + } } if (prevProps === nextProps) { return false diff --git a/packages/runtime-core/src/componentSlots.ts b/packages/runtime-core/src/componentSlots.ts index 5821f38e8..583e12db6 100644 --- a/packages/runtime-core/src/componentSlots.ts +++ b/packages/runtime-core/src/componentSlots.ts @@ -15,6 +15,9 @@ export type Slots = Readonly export type RawSlots = { [name: string]: unknown + // manual render fn hint to skip forced children updates + $stable?: boolean + // internal, indicates compiler generated slots = can skip normalization _compiled?: boolean } @@ -49,6 +52,7 @@ export function resolveSlots( } else { slots = {} for (const key in rawSlots) { + if (key === '$stable') continue const value = rawSlots[key] if (isFunction(value)) { slots[key] = normalizeSlot(key, value)