test.todo('normalizeVNode')
- test.todo('node type inference')
+ test.todo('node type/shapeFlag inference')
test.todo('cloneVNode')
-import { ComponentInstance, LifecycleHooks, currentInstance } from './component'
+import {
+ ComponentInstance,
+ LifecycleHooks,
+ currentInstance,
+ setCurrentInstance
+} from './component'
+import { applyErrorHandling, ErrorTypeStrings } from './errorHandling'
+import { warn } from './warning'
+import { capitalize } from '@vue/shared'
function injectHook(
- name: keyof LifecycleHooks,
+ type: LifecycleHooks,
hook: Function,
- target: ComponentInstance | null | void = currentInstance
+ target: ComponentInstance | null = currentInstance
) {
if (target) {
- // TODO inject a error-handling wrapped version of the hook
- // TODO also set currentInstance when calling the hook
- ;(target[name] || (target[name] = [])).push(hook)
- } else {
- // TODO warn
+ // wrap user hook with error handling logic
+ const withErrorHandling = applyErrorHandling(hook, target, type)
+ ;(target[type] || (target[type] = [])).push((...args: any[]) => {
+ // Set currentInstance during hook invocation.
+ // This assumes the hook does not synchronously trigger other hooks, which
+ // can only be false when the user does something really funky.
+ setCurrentInstance(target)
+ const res = withErrorHandling(...args)
+ setCurrentInstance(null)
+ return res
+ })
+ } else if (__DEV__) {
+ const apiName = `on${capitalize(
+ ErrorTypeStrings[name].replace(/ hook$/, '')
+ )}`
+ warn(
+ `${apiName} is called when there is no active component instance to be ` +
+ `associated with. ` +
+ `Lifecycle injection APIs can only be used during execution of setup().`
+ )
}
}
-export function onBeforeMount(hook: Function, target?: ComponentInstance) {
- injectHook('bm', hook, target)
+export function onBeforeMount(
+ hook: Function,
+ target: ComponentInstance | null = currentInstance
+) {
+ injectHook(LifecycleHooks.BEFORE_MOUNT, hook, target)
}
-export function onMounted(hook: Function, target?: ComponentInstance) {
- injectHook('m', hook, target)
+export function onMounted(
+ hook: Function,
+ target: ComponentInstance | null = currentInstance
+) {
+ injectHook(LifecycleHooks.MOUNTED, hook, target)
}
-export function onBeforeUpdate(hook: Function, target?: ComponentInstance) {
- injectHook('bu', hook, target)
+export function onBeforeUpdate(
+ hook: Function,
+ target: ComponentInstance | null = currentInstance
+) {
+ injectHook(LifecycleHooks.BEFORE_UPDATE, hook, target)
}
-export function onUpdated(hook: Function, target?: ComponentInstance) {
- injectHook('u', hook, target)
+export function onUpdated(
+ hook: Function,
+ target: ComponentInstance | null = currentInstance
+) {
+ injectHook(LifecycleHooks.UPDATED, hook, target)
}
-export function onBeforeUnmount(hook: Function, target?: ComponentInstance) {
- injectHook('bum', hook, target)
+export function onBeforeUnmount(
+ hook: Function,
+ target: ComponentInstance | null = currentInstance
+) {
+ injectHook(LifecycleHooks.BEFORE_UNMOUNT, hook, target)
}
-export function onUnmounted(hook: Function, target?: ComponentInstance) {
- injectHook('um', hook, target)
+export function onUnmounted(
+ hook: Function,
+ target: ComponentInstance | null = currentInstance
+) {
+ injectHook(LifecycleHooks.UNMOUNTED, hook, target)
}
-export function onRenderTriggered(hook: Function, target?: ComponentInstance) {
- injectHook('rtg', hook, target)
+export function onRenderTriggered(
+ hook: Function,
+ target: ComponentInstance | null = currentInstance
+) {
+ injectHook(LifecycleHooks.RENDER_TRIGGERED, hook, target)
}
-export function onRenderTracked(hook: Function, target?: ComponentInstance) {
- injectHook('rtc', hook, target)
+export function onRenderTracked(
+ hook: Function,
+ target: ComponentInstance | null = currentInstance
+) {
+ injectHook(LifecycleHooks.RENDER_TRACKED, hook, target)
}
-export function onErrorCaptured(hook: Function, target?: ComponentInstance) {
- injectHook('ec', hook, target)
+export function onErrorCaptured(
+ hook: Function,
+ target: ComponentInstance | null = currentInstance
+) {
+ injectHook(LifecycleHooks.ERROR_CAPTURED, hook, target)
}
type LifecycleHook = Function[] | null
-export interface LifecycleHooks {
- bm: LifecycleHook // beforeMount
- m: LifecycleHook // mounted
- bu: LifecycleHook // beforeUpdate
- u: LifecycleHook // updated
- bum: LifecycleHook // beforeUnmount
- um: LifecycleHook // unmounted
- da: LifecycleHook // deactivated
- a: LifecycleHook // activated
- rtg: LifecycleHook // renderTriggered
- rtc: LifecycleHook // renderTracked
- ec: LifecycleHook // errorCaptured
+export const enum LifecycleHooks {
+ BEFORE_CREATE = 'bc',
+ CREATED = 'c',
+ BEFORE_MOUNT = 'bm',
+ MOUNTED = 'm',
+ BEFORE_UPDATE = 'bu',
+ UPDATED = 'u',
+ BEFORE_UNMOUNT = 'bum',
+ UNMOUNTED = 'um',
+ DEACTIVATED = 'da',
+ ACTIVATED = 'a',
+ RENDER_TRIGGERED = 'rtg',
+ RENDER_TRACKED = 'rtc',
+ ERROR_CAPTURED = 'ec'
}
interface SetupContext {
// user namespace
user: { [key: string]: any }
-} & SetupContext &
- LifecycleHooks
+
+ // lifecycle
+ [LifecycleHooks.BEFORE_CREATE]: LifecycleHook
+ [LifecycleHooks.CREATED]: LifecycleHook
+ [LifecycleHooks.BEFORE_MOUNT]: LifecycleHook
+ [LifecycleHooks.MOUNTED]: LifecycleHook
+ [LifecycleHooks.BEFORE_UPDATE]: LifecycleHook
+ [LifecycleHooks.UPDATED]: LifecycleHook
+ [LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook
+ [LifecycleHooks.UNMOUNTED]: LifecycleHook
+ [LifecycleHooks.RENDER_TRACKED]: LifecycleHook
+ [LifecycleHooks.RENDER_TRIGGERED]: LifecycleHook
+ [LifecycleHooks.ACTIVATED]: LifecycleHook
+ [LifecycleHooks.DEACTIVATED]: LifecycleHook
+ [LifecycleHooks.ERROR_CAPTURED]: LifecycleHook
+} & SetupContext
// createComponent
// overload 1: direct setup function
renderProxy: null,
propsProxy: null,
setupContext: null,
+ effects: null,
+ provides: parent ? parent.provides : {},
+ // setup context properties
+ data: EMPTY_OBJ,
+ props: EMPTY_OBJ,
+ attrs: EMPTY_OBJ,
+ slots: EMPTY_OBJ,
+ refs: EMPTY_OBJ,
+
+ // user namespace for storing whatever the user assigns to `this`
+ user: {},
+
+ // lifecycle hooks
+ // not using enums here because it results in computed properties
+ bc: null,
+ c: null,
bm: null,
m: null,
bu: null,
rtg: null,
rtc: null,
ec: null,
- effects: null,
- provides: parent ? parent.provides : {},
-
- // public properties
- data: EMPTY_OBJ,
- props: EMPTY_OBJ,
- attrs: EMPTY_OBJ,
- slots: EMPTY_OBJ,
- refs: EMPTY_OBJ,
-
- // user namespace for storing whatever the user assigns to `this`
- user: {},
emit: (event: string, ...args: unknown[]) => {
const props = instance.vnode.props || EMPTY_OBJ
export const getCurrentInstance: () => ComponentInstance | null = () =>
currentInstance
+export const setCurrentInstance = (instance: ComponentInstance | null) => {
+ currentInstance = instance
+}
+
export function setupStatefulComponent(instance: ComponentInstance) {
const Component = instance.type as ComponentOptions
// 1. create render proxy
-// TODO
+import { VNode } from './vnode'
+import { ComponentInstance, LifecycleHooks } from './component'
+import { warn, pushWarningContext, popWarningContext } from './warning'
+
+// contexts where user provided function may be executed, in addition to
+// lifecycle hooks.
+export const enum UserExecutionContexts {
+ RENDER_FUNCTION = 1,
+ WATCH_CALLBACK,
+ NATIVE_EVENT_HANDLER,
+ COMPONENT_EVENT_HANDLER,
+ SCHEDULER
+}
+
+export const ErrorTypeStrings: Record<number | string, string> = {
+ [LifecycleHooks.BEFORE_CREATE]: 'beforeCreate hook',
+ [LifecycleHooks.CREATED]: 'created hook',
+ [LifecycleHooks.BEFORE_MOUNT]: 'beforeMount hook',
+ [LifecycleHooks.MOUNTED]: 'mounted hook',
+ [LifecycleHooks.BEFORE_UPDATE]: 'beforeUpdate hook',
+ [LifecycleHooks.UPDATED]: 'updated',
+ [LifecycleHooks.BEFORE_UNMOUNT]: 'beforeUnmount hook',
+ [LifecycleHooks.UNMOUNTED]: 'unmounted hook',
+ [LifecycleHooks.ACTIVATED]: 'activated hook',
+ [LifecycleHooks.DEACTIVATED]: 'deactivated hook',
+ [LifecycleHooks.ERROR_CAPTURED]: 'errorCaptured hook',
+ [LifecycleHooks.RENDER_TRACKED]: 'renderTracked hook',
+ [LifecycleHooks.RENDER_TRIGGERED]: 'renderTriggered hook',
+ [UserExecutionContexts.RENDER_FUNCTION]: 'render function',
+ [UserExecutionContexts.WATCH_CALLBACK]: 'watcher callback',
+ [UserExecutionContexts.NATIVE_EVENT_HANDLER]: 'native event handler',
+ [UserExecutionContexts.COMPONENT_EVENT_HANDLER]: 'component event handler',
+ [UserExecutionContexts.SCHEDULER]:
+ 'scheduler flush. This may be a Vue internals bug. ' +
+ 'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue'
+}
+
+type ErrorTypes = LifecycleHooks | UserExecutionContexts
+
+// takes a user-provided function and returns a verison that handles potential
+// errors (including async)
+export function applyErrorHandling<T extends Function>(
+ fn: T,
+ instance: ComponentInstance | null,
+ type: ErrorTypes
+): T {
+ return function errorHandlingWrapper(...args: any[]) {
+ let res: any
+ try {
+ res = fn(...args)
+ if (res && !res._isVue && typeof res.then === 'function') {
+ ;(res as Promise<any>).catch(err => {
+ handleError(err, instance, type)
+ })
+ }
+ } catch (err) {
+ handleError(err, instance, type)
+ }
+ return res
+ } as any
+}
+
+export function handleError(
+ err: Error,
+ instance: ComponentInstance | null,
+ type: ErrorTypes
+) {
+ const contextVNode = instance ? instance.vnode : null
+ let cur: ComponentInstance | null = instance && instance.parent
+ while (cur) {
+ const errorCapturedHooks = cur.ec
+ if (errorCapturedHooks !== null) {
+ for (let i = 0; i < errorCapturedHooks.length; i++) {
+ if (errorCapturedHooks[i](err, type, contextVNode)) {
+ return
+ }
+ }
+ }
+ cur = cur.parent
+ }
+ logError(err, type, contextVNode)
+}
+
+function logError(err: Error, type: ErrorTypes, contextVNode: VNode | null) {
+ if (__DEV__) {
+ const info = ErrorTypeStrings[type]
+ if (contextVNode) {
+ pushWarningContext(contextVNode)
+ }
+ warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`)
+ console.error(err)
+ if (contextVNode) {
+ popWarningContext()
+ }
+ } else {
+ throw err
+ }
+}