Explorar o código

fix(hydration): ensure hydrated event listeners have bound instance (#4529)

fix #4479
ygj6 %!s(int64=4) %!d(string=hai) anos
pai
achega
58b1fa5ed1

+ 54 - 0
packages/runtime-core/__tests__/hydration.spec.ts

@@ -461,6 +461,60 @@ describe('SSR hydration', () => {
     expect(text.textContent).toBe('bye')
     expect(text.textContent).toBe('bye')
   })
   })
 
 
+  test('handle click error in ssr mode', async () => {
+    const App = {
+      setup() {
+        const throwError = () => {
+          throw new Error('Sentry Error')
+        }
+        return { throwError }
+      },
+      template: `
+        <div>
+          <button class="parent-click" @click="throwError">click me</button>
+        </div>`
+    }
+
+    const container = document.createElement('div')
+    // server render
+    container.innerHTML = await renderToString(h(App))
+    // hydrate
+    const app = createSSRApp(App)
+    const handler = (app.config.errorHandler = jest.fn())
+    app.mount(container)
+    // assert interactions
+    // parent button click
+    triggerEvent('click', container.querySelector('.parent-click')!)
+    expect(handler).toHaveBeenCalled()
+  })
+
+  test('handle blur error in ssr mode', async () => {
+    const App = {
+      setup() {
+        const throwError = () => {
+          throw new Error('Sentry Error')
+        }
+        return { throwError }
+      },
+      template: `
+        <div>
+          <input class="parent-click" @blur="throwError"/>
+        </div>`
+    }
+
+    const container = document.createElement('div')
+    // server render
+    container.innerHTML = await renderToString(h(App))
+    // hydrate
+    const app = createSSRApp(App)
+    const handler = (app.config.errorHandler = jest.fn())
+    app.mount(container)
+    // assert interactions
+    // parent blur event
+    triggerEvent('blur', container.querySelector('.parent-click')!)
+    expect(handler).toHaveBeenCalled()
+  })
+
   test('Suspense', async () => {
   test('Suspense', async () => {
     const AsyncChild = {
     const AsyncChild = {
       async setup() {
       async setup() {

+ 18 - 2
packages/runtime-core/src/hydration.ts

@@ -287,13 +287,29 @@ export function createHydrationFunctions(
               (forcePatchValue && key.endsWith('value')) ||
               (forcePatchValue && key.endsWith('value')) ||
               (isOn(key) && !isReservedProp(key))
               (isOn(key) && !isReservedProp(key))
             ) {
             ) {
-              patchProp(el, key, null, props[key])
+              patchProp(
+                el,
+                key,
+                null,
+                props[key],
+                false,
+                undefined,
+                parentComponent
+              )
             }
             }
           }
           }
         } else if (props.onClick) {
         } else if (props.onClick) {
           // Fast path for click listeners (which is most often) to avoid
           // Fast path for click listeners (which is most often) to avoid
           // iterating through props.
           // iterating through props.
-          patchProp(el, 'onClick', null, props.onClick)
+          patchProp(
+            el,
+            'onClick',
+            null,
+            props.onClick,
+            false,
+            undefined,
+            parentComponent
+          )
         }
         }
       }
       }
       // vnode / directive hooks
       // vnode / directive hooks