From: daiwei Date: Sat, 8 Feb 2025 03:28:09 +0000 (+0800) Subject: fix(runtime-core): avoid child component update without changes X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=52c120cf3325c9a9c86212f84788a5814dc6e61c;p=thirdparty%2Fvuejs%2Fcore.git fix(runtime-core): avoid child component update without changes --- diff --git a/packages/runtime-core/__tests__/rendererComponent.spec.ts b/packages/runtime-core/__tests__/rendererComponent.spec.ts index fefc413703..ca77789ad3 100644 --- a/packages/runtime-core/__tests__/rendererComponent.spec.ts +++ b/packages/runtime-core/__tests__/rendererComponent.spec.ts @@ -6,6 +6,7 @@ import { inject, nextTick, nodeOps, + onMounted, provide, ref, render, @@ -474,4 +475,55 @@ describe('renderer: component', () => { `Property '$attrs' was accessed via 'this'. Avoid using 'this' in templates.`, ).toHaveBeenWarned() }) + + test('should not update child component without changes', async () => { + const text = ref(0) + const spy = vi.fn() + + const ClientOnly = { + setup(_: any, { slots }: SetupContext) { + const mounted = ref(false) + onMounted(() => { + mounted.value = true + }) + return () => { + if (mounted.value) { + return slots.default!() + } + } + }, + } + + const App = { + render() { + return h(ClientOnly, null, { + default: () => [ + h('span', null, [text.value]), + h(Comp, { style: { width: '100%' } }), + ], + }) + }, + } + + const Comp = { + render(this: any) { + spy() + return null + }, + } + + const root = nodeOps.createElement('div') + render(h(App), root) + expect(serializeInner(root)).toBe(``) + await nextTick() + + expect(serializeInner(root)).toBe(`0`) + expect(spy).toHaveBeenCalledTimes(1) + + text.value++ + await nextTick() + expect(serializeInner(root)).toBe(`1`) + // expect Comp to not be re-rendered + expect(spy).toHaveBeenCalledTimes(1) + }) }) diff --git a/packages/runtime-core/src/componentRenderUtils.ts b/packages/runtime-core/src/componentRenderUtils.ts index a1afae6201..f093cc328e 100644 --- a/packages/runtime-core/src/componentRenderUtils.ts +++ b/packages/runtime-core/src/componentRenderUtils.ts @@ -15,7 +15,13 @@ import { normalizeVNode, } from './vnode' import { ErrorCodes, handleError } from './errorHandling' -import { PatchFlags, ShapeFlags, isModelListener, isOn } from '@vue/shared' +import { + PatchFlags, + ShapeFlags, + isModelListener, + isOn, + looseEqual, +} from '@vue/shared' import { warn } from './warning' import { isHmrUpdating } from './hmr' import type { NormalizedProps } from './componentProps' @@ -399,7 +405,7 @@ export function shouldUpdateComponent( for (let i = 0; i < dynamicProps.length; i++) { const key = dynamicProps[i] if ( - nextProps![key] !== prevProps![key] && + !looseEqual(nextProps![key], prevProps![key]) && !isEmitListener(emits, key) ) { return true @@ -441,7 +447,7 @@ function hasPropsChanged( for (let i = 0; i < nextKeys.length; i++) { const key = nextKeys[i] if ( - nextProps[key] !== prevProps[key] && + !looseEqual(nextProps[key], prevProps[key]) && !isEmitListener(emitsOptions, key) ) { return true