]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): avoid child component update without changes
authordaiwei <daiwei521@126.com>
Sat, 8 Feb 2025 03:28:09 +0000 (11:28 +0800)
committerdaiwei <daiwei521@126.com>
Sat, 8 Feb 2025 03:28:09 +0000 (11:28 +0800)
packages/runtime-core/__tests__/rendererComponent.spec.ts
packages/runtime-core/src/componentRenderUtils.ts

index fefc4137034f98d1407958330d63a65b596a4272..ca77789ad3bc14c2ab6d1d93de23b5fa5adca182 100644 (file)
@@ -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(`<span>0</span><!---->`)
+    expect(spy).toHaveBeenCalledTimes(1)
+
+    text.value++
+    await nextTick()
+    expect(serializeInner(root)).toBe(`<span>1</span><!---->`)
+    // expect Comp to not be re-rendered
+    expect(spy).toHaveBeenCalledTimes(1)
+  })
 })
index a1afae6201a57033b70829aba8f68f86537c3c4e..f093cc328e81422ad27a0189e3c2ef8cab865225 100644 (file)
@@ -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