Explorar o código

perf(runtime-dom): optimize array event handler dispatch (#14828)

吴杨帆 hai 3 semanas
pai
achega
bb18dc8e56

+ 23 - 0
packages/runtime-dom/__tests__/patchEvents.spec.ts

@@ -43,6 +43,29 @@ describe(`runtime-dom: events patching`, () => {
     expect(fn2).toHaveBeenCalledTimes(1)
   })
 
+  it('should snapshot event handler arrays during dispatch', async () => {
+    const el = document.createElement('div')
+    const fn2 = vi.fn()
+    const fn3 = vi.fn()
+    const fn4 = vi.fn()
+    const handlers: Function[] = [
+      vi.fn(() => {
+        handlers[1] = fn3
+        handlers.push(fn4)
+      }),
+      fn2,
+    ]
+
+    patchProp(el, 'onClick', null, handlers)
+    el.dispatchEvent(new Event('click'))
+    await timeout()
+
+    expect(handlers[0]).toHaveBeenCalledTimes(1)
+    expect(fn2).toHaveBeenCalledTimes(1)
+    expect(fn3).not.toHaveBeenCalled()
+    expect(fn4).not.toHaveBeenCalled()
+  })
+
   it('should unassign event handler', async () => {
     const el = document.createElement('div')
     const fn = vi.fn()

+ 31 - 24
packages/runtime-dom/src/modules/events.ts

@@ -112,12 +112,37 @@ function createInvoker(
     } else if (e._vts <= invoker.attached) {
       return
     }
-    callWithAsyncErrorHandling(
-      patchStopImmediatePropagation(e, invoker.value),
-      instance,
-      ErrorCodes.NATIVE_EVENT_HANDLER,
-      [e],
-    )
+    const value = invoker.value
+    if (isArray(value)) {
+      const originalStop = e.stopImmediatePropagation
+      e.stopImmediatePropagation = () => {
+        originalStop.call(e)
+        ;(e as any)._stopped = true
+      }
+      const handlers = value.slice()
+      const args = [e]
+      for (let i = 0; i < handlers.length; i++) {
+        if ((e as any)._stopped) {
+          break
+        }
+        const handler = handlers[i]
+        if (handler) {
+          callWithAsyncErrorHandling(
+            handler,
+            instance,
+            ErrorCodes.NATIVE_EVENT_HANDLER,
+            args,
+          )
+        }
+      }
+    } else {
+      callWithAsyncErrorHandling(
+        value,
+        instance,
+        ErrorCodes.NATIVE_EVENT_HANDLER,
+        [e],
+      )
+    }
   }
   invoker.value = initialValue
   invoker.attached = getNow()
@@ -134,21 +159,3 @@ function sanitizeEventValue(value: unknown, propName: string): EventValue {
   )
   return NOOP
 }
-
-function patchStopImmediatePropagation(
-  e: Event,
-  value: EventValue,
-): EventValue {
-  if (isArray(value)) {
-    const originalStop = e.stopImmediatePropagation
-    e.stopImmediatePropagation = () => {
-      originalStop.call(e)
-      ;(e as any)._stopped = true
-    }
-    return (value as Function[]).map(
-      fn => (e: Event) => !(e as any)._stopped && fn && fn(e),
-    )
-  } else {
-    return value
-  }
-}