Skip to content

Commit 582a3a3

Browse files
authored
feat(runtime-core): add app.onUnmount() for registering cleanup functions (#4619)
close #4516
1 parent 801b8de commit 582a3a3

File tree

3 files changed

+50
-0
lines changed

3 files changed

+50
-0
lines changed

Diff for: packages/runtime-core/__tests__/apiCreateApp.spec.ts

+30
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,36 @@ describe('api: createApp', () => {
344344
).toHaveBeenWarnedTimes(1)
345345
})
346346

347+
test('onUnmount', () => {
348+
const cleanup = vi.fn().mockName('plugin cleanup')
349+
const PluginA: Plugin = app => {
350+
app.provide('foo', 1)
351+
app.onUnmount(cleanup)
352+
}
353+
const PluginB: Plugin = {
354+
install: (app, arg1, arg2) => {
355+
app.provide('bar', arg1 + arg2)
356+
app.onUnmount(cleanup)
357+
},
358+
}
359+
360+
const app = createApp({
361+
render: () => `Test`,
362+
})
363+
app.use(PluginA)
364+
app.use(PluginB)
365+
366+
const root = nodeOps.createElement('div')
367+
app.mount(root)
368+
369+
//also can be added after mount
370+
app.onUnmount(cleanup)
371+
372+
app.unmount()
373+
374+
expect(cleanup).toHaveBeenCalledTimes(3)
375+
})
376+
347377
test('config.errorHandler', () => {
348378
const error = new Error()
349379
const count = ref(0)

Diff for: packages/runtime-core/src/apiCreateApp.ts

+18
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { version } from '.'
2727
import { installAppCompatProperties } from './compat/global'
2828
import type { NormalizedPropsOptions } from './componentProps'
2929
import type { ObjectEmitsOptions } from './componentEmits'
30+
import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
3031
import type { DefineComponent } from './apiDefineComponent'
3132

3233
export interface App<HostElement = any> {
@@ -50,6 +51,7 @@ export interface App<HostElement = any> {
5051
namespace?: boolean | ElementNamespace,
5152
): ComponentPublicInstance
5253
unmount(): void
54+
onUnmount(cb: () => void): void
5355
provide<T>(key: InjectionKey<T> | string, value: T): this
5456

5557
/**
@@ -214,6 +216,7 @@ export function createAppAPI<HostElement>(
214216

215217
const context = createAppContext()
216218
const installedPlugins = new WeakSet()
219+
const pluginCleanupFns: Array<() => any> = []
217220

218221
let isMounted = false
219222

@@ -366,8 +369,23 @@ export function createAppAPI<HostElement>(
366369
}
367370
},
368371

372+
onUnmount(cleanupFn: () => void) {
373+
if (__DEV__ && typeof cleanupFn !== 'function') {
374+
warn(
375+
`Expected function as first argument to app.onUnmount(), ` +
376+
`but got ${typeof cleanupFn}`,
377+
)
378+
}
379+
pluginCleanupFns.push(cleanupFn)
380+
},
381+
369382
unmount() {
370383
if (isMounted) {
384+
callWithAsyncErrorHandling(
385+
pluginCleanupFns,
386+
app._instance,
387+
ErrorCodes.APP_UNMOUNT_CLEANUP,
388+
)
371389
render(null, app._container)
372390
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
373391
app._instance = null

Diff for: packages/runtime-core/src/errorHandling.ts

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export enum ErrorCodes {
2323
FUNCTION_REF,
2424
ASYNC_COMPONENT_LOADER,
2525
SCHEDULER,
26+
APP_UNMOUNT_CLEANUP,
2627
}
2728

2829
export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
@@ -57,6 +58,7 @@ export const ErrorTypeStrings: Record<LifecycleHooks | ErrorCodes, string> = {
5758
[ErrorCodes.SCHEDULER]:
5859
'scheduler flush. This is likely a Vue internals bug. ' +
5960
'Please open an issue at https://github.com/vuejs/core .',
61+
[ErrorCodes.APP_UNMOUNT_CLEANUP]: 'app unmount cleanup function',
6062
}
6163

6264
export type ErrorTypes = LifecycleHooks | ErrorCodes

0 commit comments

Comments
 (0)