diff --git a/packages/runtime-core/__tests__/apiOptions.spec.ts b/packages/runtime-core/__tests__/apiOptions.spec.ts index b0f4d147..103a56a4 100644 --- a/packages/runtime-core/__tests__/apiOptions.spec.ts +++ b/packages/runtime-core/__tests__/apiOptions.spec.ts @@ -227,7 +227,145 @@ describe('api: options', () => { expect(renderToString(h(Root))).toBe(`1112`) }) - test('lifecycle', () => {}) + test('lifecycle', async () => { + const count = ref(0) + const root = nodeOps.createElement('div') + const calls: string[] = [] + + const Root = { + beforeCreate() { + calls.push('root beforeCreate') + }, + created() { + calls.push('root created') + }, + beforeMount() { + calls.push('root onBeforeMount') + }, + mounted() { + calls.push('root onMounted') + }, + beforeUpdate() { + calls.push('root onBeforeUpdate') + }, + updated() { + calls.push('root onUpdated') + }, + beforeUnmount() { + calls.push('root onBeforeUnmount') + }, + unmounted() { + calls.push('root onUnmounted') + }, + render() { + return h(Mid, { count: count.value }) + } + } + + const Mid = { + beforeCreate() { + calls.push('mid beforeCreate') + }, + created() { + calls.push('mid created') + }, + beforeMount() { + calls.push('mid onBeforeMount') + }, + mounted() { + calls.push('mid onMounted') + }, + beforeUpdate() { + calls.push('mid onBeforeUpdate') + }, + updated() { + calls.push('mid onUpdated') + }, + beforeUnmount() { + calls.push('mid onBeforeUnmount') + }, + unmounted() { + calls.push('mid onUnmounted') + }, + render() { + return h(Child, { count: this.$props.count }) + } + } + + const Child = { + beforeCreate() { + calls.push('child beforeCreate') + }, + created() { + calls.push('child created') + }, + beforeMount() { + calls.push('child onBeforeMount') + }, + mounted() { + calls.push('child onMounted') + }, + beforeUpdate() { + calls.push('child onBeforeUpdate') + }, + updated() { + calls.push('child onUpdated') + }, + beforeUnmount() { + calls.push('child onBeforeUnmount') + }, + unmounted() { + calls.push('child onUnmounted') + }, + render() { + return h('div', this.$props.count) + } + } + + // mount + render(h(Root), root) + expect(calls).toEqual([ + 'root beforeCreate', + 'root created', + 'root onBeforeMount', + 'mid beforeCreate', + 'mid created', + 'mid onBeforeMount', + 'child beforeCreate', + 'child created', + 'child onBeforeMount', + 'child onMounted', + 'mid onMounted', + 'root onMounted' + ]) + + calls.length = 0 + + // update + count.value++ + await nextTick() + expect(calls).toEqual([ + 'root onBeforeUpdate', + 'mid onBeforeUpdate', + 'child onBeforeUpdate', + 'child onUpdated', + 'mid onUpdated', + 'root onUpdated' + ]) + + calls.length = 0 + + // unmount + render(null, root) + expect(calls).toEqual([ + 'root onBeforeUnmount', + 'mid onBeforeUnmount', + 'child onBeforeUnmount', + 'child onUnmounted', + 'mid onUnmounted', + 'root onUnmounted' + ]) + }) test('mixins', () => { const calls: string[] = [] @@ -237,8 +375,14 @@ describe('api: options', () => { a: 1 } }, + created() { + calls.push('mixinA created') + expect(this.a).toBe(1) + expect(this.b).toBe(2) + expect(this.c).toBe(3) + }, mounted() { - calls.push('mixinA') + calls.push('mixinA mounted') } } const mixinB = { @@ -247,8 +391,14 @@ describe('api: options', () => { b: 2 } }, + created() { + calls.push('mixinB created') + expect(this.a).toBe(1) + expect(this.b).toBe(2) + expect(this.c).toBe(3) + }, mounted() { - calls.push('mixinB') + calls.push('mixinB mounted') } } const Comp = { @@ -258,8 +408,14 @@ describe('api: options', () => { c: 3 } }, + created() { + calls.push('comp created') + expect(this.a).toBe(1) + expect(this.b).toBe(2) + expect(this.c).toBe(3) + }, mounted() { - calls.push('comp') + calls.push('comp mounted') }, render() { return `${this.a}${this.b}${this.c}` @@ -267,7 +423,14 @@ describe('api: options', () => { } expect(renderToString(h(Comp))).toBe(`123`) - expect(calls).toEqual(['mixinA', 'mixinB', 'comp']) + expect(calls).toEqual([ + 'mixinA created', + 'mixinB created', + 'comp created', + 'mixinA mounted', + 'mixinB mounted', + 'comp mounted' + ]) }) test('extends', () => { diff --git a/packages/runtime-core/src/apiOptions.ts b/packages/runtime-core/src/apiOptions.ts index 6070b8c3..472462cf 100644 --- a/packages/runtime-core/src/apiOptions.ts +++ b/packages/runtime-core/src/apiOptions.ts @@ -70,8 +70,8 @@ export interface LegacyOptions { updated?(): void activated?(): void decativated?(): void - beforeDestroy?(): void - destroyed?(): void + beforeUnmount?(): void + unmounted?(): void renderTracked?(e: DebuggerEvent): void renderTriggered?(e: DebuggerEvent): void errorCaptured?(): boolean | void @@ -100,25 +100,30 @@ export function applyOptions( components, directives, // lifecycle - // beforeCreate is handled separately - created, beforeMount, mounted, beforeUpdate, updated, // TODO activated // TODO decativated - beforeDestroy, - destroyed, + beforeUnmount, + unmounted, renderTracked, renderTriggered, errorCaptured } = options + const globalMixins = instance.appContext.mixins + + // beforeCreate + if (!asMixin) { + callSyncHook('beforeCreate', options, ctx, globalMixins) + } + // global mixins are applied first, and only if this is a non-mixin call // so that they are applied once per instance. if (!asMixin) { - applyMixins(instance, instance.appContext.mixins) + applyMixins(instance, globalMixins) } // extending a base component... if (extendsOptions) { @@ -205,8 +210,8 @@ export function applyOptions( } // lifecycle options - if (created) { - created.call(ctx) + if (!asMixin) { + callSyncHook('created', options, ctx, globalMixins) } if (beforeMount) { onBeforeMount(beforeMount.bind(ctx)) @@ -229,11 +234,45 @@ export function applyOptions( if (renderTriggered) { onRenderTracked(renderTriggered.bind(ctx)) } - if (beforeDestroy) { - onBeforeUnmount(beforeDestroy.bind(ctx)) + if (beforeUnmount) { + onBeforeUnmount(beforeUnmount.bind(ctx)) } - if (destroyed) { - onUnmounted(destroyed.bind(ctx)) + if (unmounted) { + onUnmounted(unmounted.bind(ctx)) + } +} + +function callSyncHook( + name: 'beforeCreate' | 'created', + options: ComponentOptions, + ctx: any, + globalMixins: ComponentOptions[] +) { + callHookFromMixins(name, globalMixins, ctx) + const baseHook = options.extends && options.extends[name] + if (baseHook) { + baseHook.call(ctx) + } + const mixins = options.mixins + if (mixins) { + callHookFromMixins(name, mixins, ctx) + } + const selfHook = options[name] + if (selfHook) { + selfHook.call(ctx) + } +} + +function callHookFromMixins( + name: 'beforeCreate' | 'created', + mixins: ComponentOptions[], + ctx: any +) { + for (let i = 0; i < mixins.length; i++) { + const fn = mixins[i][name] + if (fn) { + fn.call(ctx) + } } }