feat: error handling for setup / render / watch / event handlers
This commit is contained in:
parent
1d55b368e8
commit
966d7b5487
17
packages/runtime-core/__tests__/errorHandling.spec.ts
Normal file
17
packages/runtime-core/__tests__/errorHandling.spec.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
describe('error handling', () => {
|
||||||
|
test.todo('in lifecycle hooks')
|
||||||
|
|
||||||
|
test.todo('in onErrorCaptured')
|
||||||
|
|
||||||
|
test.todo('in setup function')
|
||||||
|
|
||||||
|
test.todo('in render function')
|
||||||
|
|
||||||
|
test.todo('in watch (simple usage)')
|
||||||
|
|
||||||
|
test.todo('in watch (with source)')
|
||||||
|
|
||||||
|
test.todo('in native event handler')
|
||||||
|
|
||||||
|
test.todo('in component event handler')
|
||||||
|
})
|
@ -5,7 +5,9 @@ export interface InjectionKey<T> extends Symbol {}
|
|||||||
|
|
||||||
export function provide<T>(key: InjectionKey<T> | string, value: T) {
|
export function provide<T>(key: InjectionKey<T> | string, value: T) {
|
||||||
if (!currentInstance) {
|
if (!currentInstance) {
|
||||||
// TODO warn
|
if (__DEV__) {
|
||||||
|
warn(`provide() is used without an active component instance.`)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let provides = currentInstance.provides
|
let provides = currentInstance.provides
|
||||||
// by default an instance inherits its parent's provides object
|
// by default an instance inherits its parent's provides object
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
currentInstance,
|
currentInstance,
|
||||||
setCurrentInstance
|
setCurrentInstance
|
||||||
} from './component'
|
} from './component'
|
||||||
import { callUserFnWithErrorHandling, ErrorTypeStrings } from './errorHandling'
|
import { callWithAsyncErrorHandling, ErrorTypeStrings } from './errorHandling'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { capitalize } from '@vue/shared'
|
import { capitalize } from '@vue/shared'
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ function injectHook(
|
|||||||
// This assumes the hook does not synchronously trigger other hooks, which
|
// This assumes the hook does not synchronously trigger other hooks, which
|
||||||
// can only be false when the user does something really funky.
|
// can only be false when the user does something really funky.
|
||||||
setCurrentInstance(target)
|
setCurrentInstance(target)
|
||||||
const res = callUserFnWithErrorHandling(hook, target, type, args)
|
const res = callWithAsyncErrorHandling(hook, target, type, args)
|
||||||
setCurrentInstance(null)
|
setCurrentInstance(null)
|
||||||
return res
|
return res
|
||||||
})
|
})
|
||||||
|
@ -9,6 +9,11 @@ import { queueJob, queuePostFlushCb } from './scheduler'
|
|||||||
import { EMPTY_OBJ, isObject, isArray, isFunction } from '@vue/shared'
|
import { EMPTY_OBJ, isObject, isArray, isFunction } from '@vue/shared'
|
||||||
import { recordEffect } from './apiReactivity'
|
import { recordEffect } from './apiReactivity'
|
||||||
import { getCurrentInstance } from './component'
|
import { getCurrentInstance } from './component'
|
||||||
|
import {
|
||||||
|
UserExecutionContexts,
|
||||||
|
callWithErrorHandling,
|
||||||
|
callWithAsyncErrorHandling
|
||||||
|
} from './errorHandling'
|
||||||
|
|
||||||
export interface WatchOptions {
|
export interface WatchOptions {
|
||||||
lazy?: boolean
|
lazy?: boolean
|
||||||
@ -78,22 +83,57 @@ function doWatch(
|
|||||||
| null,
|
| null,
|
||||||
{ lazy, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
|
{ lazy, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
|
||||||
): StopHandle {
|
): StopHandle {
|
||||||
const baseGetter = isArray(source)
|
const instance = getCurrentInstance()
|
||||||
? () => source.map(s => (isRef(s) ? s.value : s()))
|
|
||||||
: isRef(source)
|
|
||||||
? () => source.value
|
|
||||||
: () => {
|
|
||||||
if (cleanup) {
|
|
||||||
cleanup()
|
|
||||||
}
|
|
||||||
return source(registerCleanup)
|
|
||||||
}
|
|
||||||
const getter = deep ? () => traverse(baseGetter()) : baseGetter
|
|
||||||
|
|
||||||
let cleanup: any
|
let getter: Function
|
||||||
|
if (isArray(source)) {
|
||||||
|
getter = () =>
|
||||||
|
source.map(
|
||||||
|
s =>
|
||||||
|
isRef(s)
|
||||||
|
? s.value
|
||||||
|
: callWithErrorHandling(
|
||||||
|
s,
|
||||||
|
instance,
|
||||||
|
UserExecutionContexts.WATCH_GETTER
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (isRef(source)) {
|
||||||
|
getter = () => source.value
|
||||||
|
} else if (cb) {
|
||||||
|
// getter with cb
|
||||||
|
getter = () =>
|
||||||
|
callWithErrorHandling(
|
||||||
|
source,
|
||||||
|
instance,
|
||||||
|
UserExecutionContexts.WATCH_GETTER
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// no cb -> simple effect
|
||||||
|
getter = () => {
|
||||||
|
if (cleanup) {
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
return callWithErrorHandling(
|
||||||
|
source,
|
||||||
|
instance,
|
||||||
|
UserExecutionContexts.WATCH_CALLBACK,
|
||||||
|
[registerCleanup]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deep) {
|
||||||
|
const baseGetter = getter
|
||||||
|
getter = () => traverse(baseGetter())
|
||||||
|
}
|
||||||
|
|
||||||
|
let cleanup: Function
|
||||||
const registerCleanup: CleanupRegistrator = (fn: () => void) => {
|
const registerCleanup: CleanupRegistrator = (fn: () => void) => {
|
||||||
// TODO wrap the cleanup fn for error handling
|
// TODO wrap the cleanup fn for error handling
|
||||||
cleanup = runner.onStop = fn
|
cleanup = runner.onStop = () => {
|
||||||
|
callWithErrorHandling(fn, instance, UserExecutionContexts.WATCH_CLEANUP)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let oldValue = isArray(source) ? [] : undefined
|
let oldValue = isArray(source) ? [] : undefined
|
||||||
@ -105,16 +145,17 @@ function doWatch(
|
|||||||
if (cleanup) {
|
if (cleanup) {
|
||||||
cleanup()
|
cleanup()
|
||||||
}
|
}
|
||||||
// TODO handle error (including ASYNC)
|
callWithAsyncErrorHandling(
|
||||||
try {
|
cb,
|
||||||
cb(newValue, oldValue, registerCleanup)
|
instance,
|
||||||
} catch (e) {}
|
UserExecutionContexts.WATCH_CALLBACK,
|
||||||
|
[newValue, oldValue, registerCleanup]
|
||||||
|
)
|
||||||
oldValue = newValue
|
oldValue = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: void 0
|
: void 0
|
||||||
|
|
||||||
const instance = getCurrentInstance()
|
|
||||||
const scheduler =
|
const scheduler =
|
||||||
flush === 'sync'
|
flush === 'sync'
|
||||||
? invoke
|
? invoke
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
import { VNode, normalizeVNode, VNodeChild } from './vnode'
|
import { VNode, normalizeVNode, VNodeChild, createVNode, Empty } from './vnode'
|
||||||
import { ReactiveEffect, UnwrapRef, reactive, readonly } from '@vue/reactivity'
|
import { ReactiveEffect, UnwrapRef, reactive, readonly } from '@vue/reactivity'
|
||||||
import { EMPTY_OBJ, isFunction, capitalize, invokeHandlers } from '@vue/shared'
|
import { EMPTY_OBJ, isFunction, capitalize, NOOP, isArray } from '@vue/shared'
|
||||||
import { RenderProxyHandlers } from './componentProxy'
|
import { RenderProxyHandlers } from './componentProxy'
|
||||||
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
|
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
|
||||||
import { Slots } from './componentSlots'
|
import { Slots } from './componentSlots'
|
||||||
import { PatchFlags } from './patchFlags'
|
import { PatchFlags } from './patchFlags'
|
||||||
import { ShapeFlags } from './shapeFlags'
|
import { ShapeFlags } from './shapeFlags'
|
||||||
|
import { warn } from './warning'
|
||||||
|
import {
|
||||||
|
UserExecutionContexts,
|
||||||
|
handleError,
|
||||||
|
callWithErrorHandling,
|
||||||
|
callWithAsyncErrorHandling
|
||||||
|
} from './errorHandling'
|
||||||
|
|
||||||
export type Data = { [key: string]: unknown }
|
export type Data = { [key: string]: unknown }
|
||||||
|
|
||||||
@ -226,7 +233,23 @@ export function createComponentInstance(
|
|||||||
const props = instance.vnode.props || EMPTY_OBJ
|
const props = instance.vnode.props || EMPTY_OBJ
|
||||||
const handler = props[`on${event}`] || props[`on${capitalize(event)}`]
|
const handler = props[`on${event}`] || props[`on${capitalize(event)}`]
|
||||||
if (handler) {
|
if (handler) {
|
||||||
invokeHandlers(handler, args)
|
if (isArray(handler)) {
|
||||||
|
for (let i = 0; i < handler.length; i++) {
|
||||||
|
callWithAsyncErrorHandling(
|
||||||
|
handler[i],
|
||||||
|
instance,
|
||||||
|
UserExecutionContexts.COMPONENT_EVENT_HANDLER,
|
||||||
|
args
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callWithAsyncErrorHandling(
|
||||||
|
handler,
|
||||||
|
instance,
|
||||||
|
UserExecutionContexts.COMPONENT_EVENT_HANDLER,
|
||||||
|
args
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,7 +284,12 @@ export function setupStatefulComponent(instance: ComponentInstance) {
|
|||||||
setup.length > 1 ? createSetupContext(instance) : null)
|
setup.length > 1 ? createSetupContext(instance) : null)
|
||||||
|
|
||||||
currentInstance = instance
|
currentInstance = instance
|
||||||
const setupResult = setup.call(null, propsProxy, setupContext)
|
const setupResult = callWithErrorHandling(
|
||||||
|
setup,
|
||||||
|
instance,
|
||||||
|
UserExecutionContexts.SETUP_FUNCTION,
|
||||||
|
[propsProxy, setupContext]
|
||||||
|
)
|
||||||
currentInstance = null
|
currentInstance = null
|
||||||
|
|
||||||
if (isFunction(setupResult)) {
|
if (isFunction(setupResult)) {
|
||||||
@ -272,9 +300,12 @@ export function setupStatefulComponent(instance: ComponentInstance) {
|
|||||||
// assuming a render function compiled from template is present.
|
// assuming a render function compiled from template is present.
|
||||||
instance.data = reactive(setupResult || {})
|
instance.data = reactive(setupResult || {})
|
||||||
if (__DEV__ && !Component.render) {
|
if (__DEV__ && !Component.render) {
|
||||||
// TODO warn missing render fn
|
warn(
|
||||||
|
`Component is missing render function. Either provide a template or ` +
|
||||||
|
`return a render function from setup().`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
instance.render = Component.render as RenderFunction
|
instance.render = (Component.render || NOOP) as RenderFunction
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (__DEV__ && !Component.render) {
|
if (__DEV__ && !Component.render) {
|
||||||
@ -327,23 +358,32 @@ export function renderComponentRoot(instance: ComponentInstance): VNode {
|
|||||||
refs,
|
refs,
|
||||||
emit
|
emit
|
||||||
} = instance
|
} = instance
|
||||||
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
|
try {
|
||||||
return normalizeVNode(
|
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
|
||||||
(instance.render as RenderFunction).call(renderProxy, props, setupContext)
|
return normalizeVNode(
|
||||||
)
|
(instance.render as RenderFunction).call(
|
||||||
} else {
|
renderProxy,
|
||||||
// functional
|
props,
|
||||||
const render = Component as FunctionalComponent
|
setupContext
|
||||||
return normalizeVNode(
|
)
|
||||||
render.length > 1
|
)
|
||||||
? render(props, {
|
} else {
|
||||||
attrs,
|
// functional
|
||||||
slots,
|
const render = Component as FunctionalComponent
|
||||||
refs,
|
return normalizeVNode(
|
||||||
emit
|
render.length > 1
|
||||||
})
|
? render(props, {
|
||||||
: render(props, null as any)
|
attrs,
|
||||||
)
|
slots,
|
||||||
|
refs,
|
||||||
|
emit
|
||||||
|
})
|
||||||
|
: render(props, null as any)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
handleError(err, instance, UserExecutionContexts.RENDER_FUNCTION)
|
||||||
|
return createVNode(Empty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,8 +5,11 @@ import { warn, pushWarningContext, popWarningContext } from './warning'
|
|||||||
// contexts where user provided function may be executed, in addition to
|
// contexts where user provided function may be executed, in addition to
|
||||||
// lifecycle hooks.
|
// lifecycle hooks.
|
||||||
export const enum UserExecutionContexts {
|
export const enum UserExecutionContexts {
|
||||||
RENDER_FUNCTION = 1,
|
SETUP_FUNCTION = 1,
|
||||||
|
RENDER_FUNCTION,
|
||||||
|
WATCH_GETTER,
|
||||||
WATCH_CALLBACK,
|
WATCH_CALLBACK,
|
||||||
|
WATCH_CLEANUP,
|
||||||
NATIVE_EVENT_HANDLER,
|
NATIVE_EVENT_HANDLER,
|
||||||
COMPONENT_EVENT_HANDLER,
|
COMPONENT_EVENT_HANDLER,
|
||||||
SCHEDULER
|
SCHEDULER
|
||||||
@ -26,8 +29,11 @@ export const ErrorTypeStrings: Record<number | string, string> = {
|
|||||||
[LifecycleHooks.ERROR_CAPTURED]: 'errorCaptured hook',
|
[LifecycleHooks.ERROR_CAPTURED]: 'errorCaptured hook',
|
||||||
[LifecycleHooks.RENDER_TRACKED]: 'renderTracked hook',
|
[LifecycleHooks.RENDER_TRACKED]: 'renderTracked hook',
|
||||||
[LifecycleHooks.RENDER_TRIGGERED]: 'renderTriggered hook',
|
[LifecycleHooks.RENDER_TRIGGERED]: 'renderTriggered hook',
|
||||||
|
[UserExecutionContexts.SETUP_FUNCTION]: 'setup function',
|
||||||
[UserExecutionContexts.RENDER_FUNCTION]: 'render function',
|
[UserExecutionContexts.RENDER_FUNCTION]: 'render function',
|
||||||
|
[UserExecutionContexts.WATCH_GETTER]: 'watcher getter',
|
||||||
[UserExecutionContexts.WATCH_CALLBACK]: 'watcher callback',
|
[UserExecutionContexts.WATCH_CALLBACK]: 'watcher callback',
|
||||||
|
[UserExecutionContexts.WATCH_CLEANUP]: 'watcher cleanup function',
|
||||||
[UserExecutionContexts.NATIVE_EVENT_HANDLER]: 'native event handler',
|
[UserExecutionContexts.NATIVE_EVENT_HANDLER]: 'native event handler',
|
||||||
[UserExecutionContexts.COMPONENT_EVENT_HANDLER]: 'component event handler',
|
[UserExecutionContexts.COMPONENT_EVENT_HANDLER]: 'component event handler',
|
||||||
[UserExecutionContexts.SCHEDULER]:
|
[UserExecutionContexts.SCHEDULER]:
|
||||||
@ -37,7 +43,7 @@ export const ErrorTypeStrings: Record<number | string, string> = {
|
|||||||
|
|
||||||
type ErrorTypes = LifecycleHooks | UserExecutionContexts
|
type ErrorTypes = LifecycleHooks | UserExecutionContexts
|
||||||
|
|
||||||
export function callUserFnWithErrorHandling(
|
export function callWithErrorHandling(
|
||||||
fn: Function,
|
fn: Function,
|
||||||
instance: ComponentInstance | null,
|
instance: ComponentInstance | null,
|
||||||
type: ErrorTypes,
|
type: ErrorTypes,
|
||||||
@ -46,17 +52,27 @@ export function callUserFnWithErrorHandling(
|
|||||||
let res: any
|
let res: any
|
||||||
try {
|
try {
|
||||||
res = args ? fn(...args) : fn()
|
res = args ? fn(...args) : fn()
|
||||||
if (res && !res._isVue && typeof res.then === 'function') {
|
|
||||||
;(res as Promise<any>).catch(err => {
|
|
||||||
handleError(err, instance, type)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
handleError(err, instance, type)
|
handleError(err, instance, type)
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function callWithAsyncErrorHandling(
|
||||||
|
fn: Function,
|
||||||
|
instance: ComponentInstance | null,
|
||||||
|
type: ErrorTypes,
|
||||||
|
args?: any[]
|
||||||
|
) {
|
||||||
|
const res = callWithErrorHandling(fn, instance, type, args)
|
||||||
|
if (res != null && !res._isVue && typeof res.then === 'function') {
|
||||||
|
;(res as Promise<any>).catch(err => {
|
||||||
|
handleError(err, instance, type)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
export function handleError(
|
export function handleError(
|
||||||
err: Error,
|
err: Error,
|
||||||
instance: ComponentInstance | null,
|
instance: ComponentInstance | null,
|
||||||
@ -68,7 +84,13 @@ export function handleError(
|
|||||||
const errorCapturedHooks = cur.ec
|
const errorCapturedHooks = cur.ec
|
||||||
if (errorCapturedHooks !== null) {
|
if (errorCapturedHooks !== null) {
|
||||||
for (let i = 0; i < errorCapturedHooks.length; i++) {
|
for (let i = 0; i < errorCapturedHooks.length; i++) {
|
||||||
if (errorCapturedHooks[i](err, type, contextVNode)) {
|
if (
|
||||||
|
errorCapturedHooks[i](
|
||||||
|
err,
|
||||||
|
instance && instance.renderProxy,
|
||||||
|
contextVNode
|
||||||
|
)
|
||||||
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,16 @@ export { getCurrentInstance } from './component'
|
|||||||
|
|
||||||
// For custom renderers
|
// For custom renderers
|
||||||
export { createRenderer } from './createRenderer'
|
export { createRenderer } from './createRenderer'
|
||||||
|
export {
|
||||||
|
handleError,
|
||||||
|
callWithErrorHandling,
|
||||||
|
callWithAsyncErrorHandling
|
||||||
|
} from './errorHandling'
|
||||||
|
|
||||||
// Types -----------------------------------------------------------------------
|
// Types -----------------------------------------------------------------------
|
||||||
|
|
||||||
export { VNode } from './vnode'
|
export { VNode } from './vnode'
|
||||||
export { FunctionalComponent } from './component'
|
export { FunctionalComponent, ComponentInstance } from './component'
|
||||||
export { RendererOptions } from './createRenderer'
|
export { RendererOptions } from './createRenderer'
|
||||||
export { Slot, Slots } from './componentSlots'
|
export { Slot, Slots } from './componentSlots'
|
||||||
export { PropType, ComponentPropsOptions } from './componentProps'
|
export { PropType, ComponentPropsOptions } from './componentProps'
|
||||||
|
@ -27,6 +27,10 @@ export function warn(msg: string, ...args: any[]) {
|
|||||||
if (!trace.length) {
|
if (!trace.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// avoid spamming test output
|
||||||
|
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'test') {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (trace.length > 1 && console.groupCollapsed) {
|
if (trace.length > 1 && console.groupCollapsed) {
|
||||||
console.groupCollapsed('at', ...formatTraceEntry(trace[0]))
|
console.groupCollapsed('at', ...formatTraceEntry(trace[0]))
|
||||||
const logs: string[] = []
|
const logs: string[] = []
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
import { invokeHandlers } from '@vue/shared'
|
import { isArray } from '@vue/shared'
|
||||||
|
import {
|
||||||
|
ComponentInstance,
|
||||||
|
callWithAsyncErrorHandling
|
||||||
|
} from '@vue/runtime-core'
|
||||||
|
import { UserExecutionContexts } from 'packages/runtime-core/src/errorHandling'
|
||||||
|
|
||||||
interface Invoker extends Function {
|
interface Invoker extends Function {
|
||||||
value: EventValue
|
value: EventValue
|
||||||
@ -39,7 +44,8 @@ export function patchEvent(
|
|||||||
el: Element,
|
el: Element,
|
||||||
name: string,
|
name: string,
|
||||||
prevValue: EventValue | null,
|
prevValue: EventValue | null,
|
||||||
nextValue: EventValue | null
|
nextValue: EventValue | null,
|
||||||
|
instance: ComponentInstance | null
|
||||||
) {
|
) {
|
||||||
const invoker = prevValue && prevValue.invoker
|
const invoker = prevValue && prevValue.invoker
|
||||||
if (nextValue) {
|
if (nextValue) {
|
||||||
@ -49,14 +55,14 @@ export function patchEvent(
|
|||||||
nextValue.invoker = invoker
|
nextValue.invoker = invoker
|
||||||
invoker.lastUpdated = getNow()
|
invoker.lastUpdated = getNow()
|
||||||
} else {
|
} else {
|
||||||
el.addEventListener(name, createInvoker(nextValue))
|
el.addEventListener(name, createInvoker(nextValue, instance))
|
||||||
}
|
}
|
||||||
} else if (invoker) {
|
} else if (invoker) {
|
||||||
el.removeEventListener(name, invoker as any)
|
el.removeEventListener(name, invoker as any)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createInvoker(value: any) {
|
function createInvoker(value: any, instance: ComponentInstance | null) {
|
||||||
const invoker = ((e: Event) => {
|
const invoker = ((e: Event) => {
|
||||||
// async edge case #6566: inner click event triggers patch, event handler
|
// async edge case #6566: inner click event triggers patch, event handler
|
||||||
// attached to outer element during patch, and triggered again. This
|
// attached to outer element during patch, and triggered again. This
|
||||||
@ -65,7 +71,24 @@ function createInvoker(value: any) {
|
|||||||
// and the handler would only fire if the event passed to it was fired
|
// and the handler would only fire if the event passed to it was fired
|
||||||
// AFTER it was attached.
|
// AFTER it was attached.
|
||||||
if (e.timeStamp >= invoker.lastUpdated) {
|
if (e.timeStamp >= invoker.lastUpdated) {
|
||||||
invokeHandlers(invoker.value, [e])
|
const args = [e]
|
||||||
|
if (isArray(value)) {
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
callWithAsyncErrorHandling(
|
||||||
|
value[i],
|
||||||
|
instance,
|
||||||
|
UserExecutionContexts.NATIVE_EVENT_HANDLER,
|
||||||
|
args
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callWithAsyncErrorHandling(
|
||||||
|
value,
|
||||||
|
instance,
|
||||||
|
UserExecutionContexts.NATIVE_EVENT_HANDLER,
|
||||||
|
args
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}) as any
|
}) as any
|
||||||
invoker.value = value
|
invoker.value = value
|
||||||
|
@ -26,7 +26,13 @@ export function patchProp(
|
|||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
if (isOn(key)) {
|
if (isOn(key)) {
|
||||||
patchEvent(el, key.slice(2).toLowerCase(), prevValue, nextValue)
|
patchEvent(
|
||||||
|
el,
|
||||||
|
key.slice(2).toLowerCase(),
|
||||||
|
prevValue,
|
||||||
|
nextValue,
|
||||||
|
parentComponent
|
||||||
|
)
|
||||||
} else if (!isSVG && key in el) {
|
} else if (!isSVG && key in el) {
|
||||||
patchDOMProp(
|
patchDOMProp(
|
||||||
el,
|
el,
|
||||||
|
@ -40,16 +40,3 @@ export const hyphenate = (str: string): string => {
|
|||||||
export const capitalize = (str: string): string => {
|
export const capitalize = (str: string): string => {
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invokeHandlers(
|
|
||||||
handlers: Function | Function[],
|
|
||||||
args: any[] = EMPTY_ARR
|
|
||||||
) {
|
|
||||||
if (isArray(handlers)) {
|
|
||||||
for (let i = 0; i < handlers.length; i++) {
|
|
||||||
handlers[i].apply(null, args)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
handlers.apply(null, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user