Skip to content

Commit 1a13c63

Browse files
committed
wip: provide/inject
1 parent dae380d commit 1a13c63

File tree

6 files changed

+427
-25
lines changed

6 files changed

+427
-25
lines changed

src/core/instance/inject.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1-
import { hasOwn } from 'shared/util'
21
import { warn, hasSymbol, isFunction } from '../util/index'
32
import { defineReactive, toggleObserving } from '../observer/index'
43
import type { Component } from 'typescript/component'
4+
import { provide } from 'v3/apiInject'
5+
import { setCurrentInstance } from '../../v3/currentInstance'
56

67
export function initProvide(vm: Component) {
7-
const provide = vm.$options.provide
8-
if (provide) {
9-
vm._provided = isFunction(provide) ? provide.call(vm) : provide
8+
const provideOption = vm.$options.provide
9+
if (provideOption) {
10+
const provided = isFunction(provideOption)
11+
? provideOption.call(vm)
12+
: provideOption
13+
const keys = hasSymbol ? Reflect.ownKeys(provided) : Object.keys(provided)
14+
setCurrentInstance(vm)
15+
for (let i = 0; i < keys.length; i++) {
16+
provide(keys[i], provided[keys[i]])
17+
}
18+
setCurrentInstance()
1019
}
1120
}
1221

@@ -47,24 +56,15 @@ export function resolveInject(
4756
// #6574 in case the inject object is observed...
4857
if (key === '__ob__') continue
4958
const provideKey = inject[key].from
50-
let source = vm
51-
while (source) {
52-
if (source._provided && hasOwn(source._provided, provideKey)) {
53-
result[key] = source._provided[provideKey]
54-
break
55-
}
56-
// @ts-expect-error
57-
source = source.$parent
58-
}
59-
if (!source) {
60-
if ('default' in inject[key]) {
61-
const provideDefault = inject[key].default
62-
result[key] = isFunction(provideDefault)
63-
? provideDefault.call(vm)
64-
: provideDefault
65-
} else if (__DEV__) {
66-
warn(`Injection "${key as string}" not found`, vm)
67-
}
59+
if (provideKey in vm._provided) {
60+
result[key] = vm._provided[provideKey]
61+
} else if ('default' in inject[key]) {
62+
const provideDefault = inject[key].default
63+
result[key] = isFunction(provideDefault)
64+
? provideDefault.call(vm)
65+
: provideDefault
66+
} else if (__DEV__) {
67+
warn(`Injection "${key as string}" not found`, vm)
6868
}
6969
}
7070
return result

src/core/instance/lifecycle.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export function initLifecycle(vm: Component) {
4949
vm.$children = []
5050
vm.$refs = {}
5151

52+
vm._provided = parent ? parent._provided : Object.create(null)
5253
vm._watcher = null
5354
vm._inactive = null
5455
vm._directInactive = false

src/v3/apiInject.ts

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,66 @@
1-
export function provide() {}
1+
import { isFunction, warn } from 'core/util'
2+
import { currentInstance } from './currentInstance'
23

3-
export function inject() {}
4+
export interface InjectionKey<T> extends Symbol {}
5+
6+
export function provide<T>(key: InjectionKey<T> | string | number, value: T) {
7+
if (!currentInstance) {
8+
if (__DEV__) {
9+
warn(`provide() can only be used inside setup().`)
10+
}
11+
} else {
12+
let provides = currentInstance._provided
13+
// by default an instance inherits its parent's provides object
14+
// but when it needs to provide values of its own, it creates its
15+
// own provides object using parent provides object as prototype.
16+
// this way in `inject` we can simply look up injections from direct
17+
// parent and let the prototype chain do the work.
18+
const parentProvides =
19+
currentInstance.$parent && currentInstance.$parent._provided
20+
if (parentProvides === provides) {
21+
provides = currentInstance._provided = Object.create(parentProvides)
22+
}
23+
// TS doesn't allow symbol as index type
24+
provides[key as string] = value
25+
}
26+
}
27+
28+
export function inject<T>(key: InjectionKey<T> | string): T | undefined
29+
export function inject<T>(
30+
key: InjectionKey<T> | string,
31+
defaultValue: T,
32+
treatDefaultAsFactory?: false
33+
): T
34+
export function inject<T>(
35+
key: InjectionKey<T> | string,
36+
defaultValue: T | (() => T),
37+
treatDefaultAsFactory: true
38+
): T
39+
export function inject(
40+
key: InjectionKey<any> | string,
41+
defaultValue?: unknown,
42+
treatDefaultAsFactory = false
43+
) {
44+
// fallback to `currentRenderingInstance` so that this can be called in
45+
// a functional component
46+
const instance = currentInstance
47+
if (instance) {
48+
// #2400
49+
// to support `app.use` plugins,
50+
// fallback to appContext's `provides` if the instance is at root
51+
const provides = instance.$parent && instance.$parent._provided
52+
53+
if (provides && (key as string | symbol) in provides) {
54+
// TS doesn't allow symbol as index type
55+
return provides[key as string]
56+
} else if (arguments.length > 1) {
57+
return treatDefaultAsFactory && isFunction(defaultValue)
58+
? defaultValue.call(instance)
59+
: defaultValue
60+
} else if (__DEV__) {
61+
warn(`injection "${String(key)}" not found.`)
62+
}
63+
} else if (__DEV__) {
64+
warn(`inject() can only be used inside setup() or functional components.`)
65+
}
66+
}

src/v3/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ export {
6969
} from 'core/observer/dep'
7070
export { TrackOpTypes, TriggerOpTypes } from './reactivity/operations'
7171

72+
export { provide, inject, InjectionKey } from './apiInject'
73+
7274
export { h } from './h'
7375
export { getCurrentInstance } from './currentInstance'
7476
export { useSlots, useAttrs } from './apiSetup'

0 commit comments

Comments
 (0)