diff --git a/packages/runtime-core/__tests__/vnode.spec.ts b/packages/runtime-core/__tests__/vnode.spec.ts index e3b8ad9fe..6cdc6f7b6 100644 --- a/packages/runtime-core/__tests__/vnode.spec.ts +++ b/packages/runtime-core/__tests__/vnode.spec.ts @@ -15,7 +15,7 @@ describe('vnode', () => { test.todo('normalizeVNode') - test.todo('node type inference') + test.todo('node type/shapeFlag inference') test.todo('cloneVNode') diff --git a/packages/runtime-core/src/apiLifecycle.ts b/packages/runtime-core/src/apiLifecycle.ts index c59760158..d1e95cfc7 100644 --- a/packages/runtime-core/src/apiLifecycle.ts +++ b/packages/runtime-core/src/apiLifecycle.ts @@ -1,51 +1,101 @@ -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) } diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 8b27fe863..f4e0d204a 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -74,18 +74,20 @@ export interface FunctionalComponent
{ 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 { @@ -116,8 +118,22 @@ export type ComponentInstance
= {
// 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
@@ -177,7 +193,23 @@ export function createComponentInstance(
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,
@@ -189,18 +221,6 @@ export function createComponentInstance(
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
@@ -220,6 +240,10 @@ export let currentInstance: ComponentInstance | null = null
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
diff --git a/packages/runtime-core/src/errorHandling.ts b/packages/runtime-core/src/errorHandling.ts
index 70b786d12..202051328 100644
--- a/packages/runtime-core/src/errorHandling.ts
+++ b/packages/runtime-core/src/errorHandling.ts
@@ -1 +1,98 @@
-// 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