Skip to content

Commit 869f3fb

Browse files
authored
feat(app): app.runWithContext() (#7451)
1 parent 2a9e379 commit 869f3fb

File tree

3 files changed

+47
-4
lines changed

3 files changed

+47
-4
lines changed

packages/runtime-core/__tests__/apiCreateApp.spec.ts

+16
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,22 @@ describe('api: createApp', () => {
110110
expect(`App already provides property with key "bar".`).toHaveBeenWarned()
111111
})
112112

113+
test('runWithContext', () => {
114+
const app = createApp({
115+
setup() {
116+
provide('foo', 'should not be seen')
117+
return () => h('div')
118+
}
119+
})
120+
app.provide('foo', 1)
121+
122+
expect(app.runWithContext(() => inject('foo'))).toBe(1)
123+
124+
// ensure the context is restored
125+
inject('foo')
126+
expect('inject() can only be used inside setup').toHaveBeenWarned()
127+
})
128+
113129
test('component', () => {
114130
const Root = {
115131
// local override

packages/runtime-core/src/apiCreateApp.ts

+23
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ export interface App<HostElement = any> {
5151
unmount(): void
5252
provide<T>(key: InjectionKey<T> | string, value: T): this
5353

54+
/**
55+
* Runs a function with the app as active instance. This allows using of `inject()` within the function to get access
56+
* to variables provided via `app.provide()`.
57+
*
58+
* @param fn - function to run with the app as active instance
59+
*/
60+
runWithContext<T>(fn: () => T): T
61+
5462
// internal, but we need to expose these for the server-renderer and devtools
5563
_uid: number
5664
_component: ConcreteComponent
@@ -370,6 +378,15 @@ export function createAppAPI<HostElement>(
370378
context.provides[key as string | symbol] = value
371379

372380
return app
381+
},
382+
383+
runWithContext(fn) {
384+
currentApp = app
385+
try {
386+
return fn()
387+
} finally {
388+
currentApp = null
389+
}
373390
}
374391
})
375392

@@ -380,3 +397,9 @@ export function createAppAPI<HostElement>(
380397
return app
381398
}
382399
}
400+
401+
/**
402+
* @internal Used to identify the current app when using `inject()` within
403+
* `app.runWithContext()`.
404+
*/
405+
export let currentApp: App<unknown> | null = null

packages/runtime-core/src/apiInject.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { isFunction } from '@vue/shared'
22
import { currentInstance } from './component'
33
import { currentRenderingInstance } from './componentRenderContext'
4+
import { currentApp } from './apiCreateApp'
45
import { warn } from './warning'
56

67
export interface InjectionKey<T> extends Symbol {}
@@ -46,21 +47,24 @@ export function inject(
4647
// fallback to `currentRenderingInstance` so that this can be called in
4748
// a functional component
4849
const instance = currentInstance || currentRenderingInstance
49-
if (instance) {
50+
51+
// also support looking up from app-level provides w/ `app.runWithContext()`
52+
if (instance || currentApp) {
5053
// #2400
5154
// to support `app.use` plugins,
5255
// fallback to appContext's `provides` if the instance is at root
53-
const provides =
54-
instance.parent == null
56+
const provides = instance
57+
? instance.parent == null
5558
? instance.vnode.appContext && instance.vnode.appContext.provides
5659
: instance.parent.provides
60+
: currentApp!._context.provides
5761

5862
if (provides && (key as string | symbol) in provides) {
5963
// TS doesn't allow symbol as index type
6064
return provides[key as string]
6165
} else if (arguments.length > 1) {
6266
return treatDefaultAsFactory && isFunction(defaultValue)
63-
? defaultValue.call(instance.proxy)
67+
? defaultValue.call(instance && instance.proxy)
6468
: defaultValue
6569
} else if (__DEV__) {
6670
warn(`injection "${String(key)}" not found.`)

0 commit comments

Comments
 (0)