]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): avoid repeated hydration mismatch checks (#14857)
authoredison <daiwei521@126.com>
Wed, 27 May 2026 06:29:46 +0000 (14:29 +0800)
committerGitHub <noreply@github.com>
Wed, 27 May 2026 06:29:46 +0000 (14:29 +0800)
close #14855

packages/runtime-core/__tests__/hydration.spec.ts
packages/runtime-core/src/hydration.ts

index 92b2edb7833d329c43a4ab581342c649e82ee3b3..2fe64910403d7d95642d8660c8419f2e7811ea87 100644 (file)
@@ -2143,6 +2143,53 @@ describe('SSR hydration', () => {
       expect(`Hydration children mismatch`).toHaveBeenWarned()
     })
 
+    test('children mismatch is checked once when removing excess nodes', () => {
+      const hasAttribute = vi.spyOn(Element.prototype, 'hasAttribute')
+
+      try {
+        const { container } = mountWithHydration(
+          `<div><span>foo</span><span>bar</span><span>baz</span></div>`,
+          () => h('div', [h('span', 'foo')]),
+        )
+        const el = container.firstChild as Element
+        const allowMismatchCheckCount = hasAttribute.mock.calls.filter(
+          ([key], i) =>
+            key === 'data-allow-mismatch' &&
+            hasAttribute.mock.contexts[i] === el,
+        ).length
+
+        expect(container.innerHTML).toBe('<div><span>foo</span></div>')
+        expect(`Hydration children mismatch`).toHaveBeenWarnedTimes(1)
+        expect(allowMismatchCheckCount).toBe(1)
+      } finally {
+        hasAttribute.mockRestore()
+      }
+    })
+
+    test('children mismatch is checked once when mounting missing nodes', () => {
+      const hasAttribute = vi.spyOn(Element.prototype, 'hasAttribute')
+
+      try {
+        const { container } = mountWithHydration(`<div></div>`, () =>
+          h('div', [h('span', 'foo'), h('span', 'bar'), h('span', 'baz')]),
+        )
+        const el = container.firstChild as Element
+        const allowMismatchCheckCount = hasAttribute.mock.calls.filter(
+          ([key], i) =>
+            key === 'data-allow-mismatch' &&
+            hasAttribute.mock.contexts[i] === el,
+        ).length
+
+        expect(container.innerHTML).toBe(
+          '<div><span>foo</span><span>bar</span><span>baz</span></div>',
+        )
+        expect(`Hydration children mismatch`).toHaveBeenWarnedTimes(1)
+        expect(allowMismatchCheckCount).toBe(1)
+      } finally {
+        hasAttribute.mockRestore()
+      }
+    })
+
     test('complete mismatch', () => {
       const { container } = mountWithHydration(
         `<div><span>foo</span><span>bar</span></div>`,
index a687d28d38070fcdafa324f0121f23c7cbd334e2..464db7519b60bb2be28da7b9108fa7d4c93e91c4 100644 (file)
@@ -427,23 +427,16 @@ export function createHydrationFunctions(
           slotScopeIds,
           optimized,
         )
-        let hasWarned = false
+        if (next && !isMismatchAllowed(el, MismatchTypes.CHILDREN)) {
+          ;(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
+            warn(
+              `Hydration children mismatch on`,
+              el,
+              `\nServer rendered element contains more child nodes than client vdom.`,
+            )
+          logMismatchError()
+        }
         while (next) {
-          if (!isMismatchAllowed(el, MismatchTypes.CHILDREN)) {
-            if (
-              (__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
-              !hasWarned
-            ) {
-              warn(
-                `Hydration children mismatch on`,
-                el,
-                `\nServer rendered element contains more child nodes than client vdom.`,
-              )
-              hasWarned = true
-            }
-            logMismatchError()
-          }
-
           // The SSRed DOM contains more nodes than it should. Remove them.
           const cur = next
           next = next.nextSibling
@@ -567,7 +560,7 @@ export function createHydrationFunctions(
     optimized = optimized || !!parentVNode.dynamicChildren
     const children = parentVNode.children as VNode[]
     const l = children.length
-    let hasWarned = false
+    let hasCheckedMismatch = false
     for (let i = 0; i < l; i++) {
       const vnode = optimized
         ? children[i]
@@ -605,19 +598,17 @@ export function createHydrationFunctions(
         // because server rendered HTML won't contain a text node
         insert((vnode.el = createText('')), container)
       } else {
-        if (!isMismatchAllowed(container, MismatchTypes.CHILDREN)) {
-          if (
-            (__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
-            !hasWarned
-          ) {
-            warn(
-              `Hydration children mismatch on`,
-              container,
-              `\nServer rendered element contains fewer child nodes than client vdom.`,
-            )
-            hasWarned = true
+        if (!hasCheckedMismatch) {
+          hasCheckedMismatch = true
+          if (!isMismatchAllowed(container, MismatchTypes.CHILDREN)) {
+            ;(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
+              warn(
+                `Hydration children mismatch on`,
+                container,
+                `\nServer rendered element contains fewer child nodes than client vdom.`,
+              )
+            logMismatchError()
           }
-          logMismatchError()
         }
 
         // the SSRed DOM didn't contain enough nodes. Mount the missing ones.