Skip to content

Commit

Permalink
fix(inject): fix edge case of provided with async-mutated getters
Browse files Browse the repository at this point in the history
fix #12667
  • Loading branch information
yyx990803 committed Jul 16, 2022
1 parent 25ffdb6 commit ea5d0f3
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 18 deletions.
15 changes: 10 additions & 5 deletions src/core/instance/inject.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { warn, hasSymbol, isFunction, isObject } from '../util/index'
import { defineReactive, toggleObserving } from '../observer/index'
import type { Component } from 'types/component'
import { provide } from 'v3/apiInject'
import { setCurrentInstance } from '../../v3/currentInstance'
import { resolveProvided } from 'v3/apiInject'

export function initProvide(vm: Component) {
const provideOption = vm.$options.provide
Expand All @@ -13,12 +12,18 @@ export function initProvide(vm: Component) {
if (!isObject(provided)) {
return
}
const source = resolveProvided(vm)
// IE9 doesn't support Object.getOwnPropertyDescriptors so we have to
// iterate the keys ourselves.
const keys = hasSymbol ? Reflect.ownKeys(provided) : Object.keys(provided)
setCurrentInstance(vm)
for (let i = 0; i < keys.length; i++) {
provide(keys[i], provided[keys[i]])
const key = keys[i]
Object.defineProperty(
source,
key,
Object.getOwnPropertyDescriptor(provided, key)!
)
}
setCurrentInstance()
}
}

Expand Down
29 changes: 17 additions & 12 deletions src/v3/apiInject.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { isFunction, warn } from 'core/util'
import { currentInstance } from './currentInstance'
import type { Component } from 'types/component'

export interface InjectionKey<T> extends Symbol {}

Expand All @@ -9,19 +10,23 @@ export function provide<T>(key: InjectionKey<T> | string | number, value: T) {
warn(`provide() can only be used inside setup().`)
}
} else {
let provides = currentInstance._provided
// by default an instance inherits its parent's provides object
// but when it needs to provide values of its own, it creates its
// own provides object using parent provides object as prototype.
// this way in `inject` we can simply look up injections from direct
// parent and let the prototype chain do the work.
const parentProvides =
currentInstance.$parent && currentInstance.$parent._provided
if (parentProvides === provides) {
provides = currentInstance._provided = Object.create(parentProvides)
}
// TS doesn't allow symbol as index type
provides[key as string] = value
resolveProvided(currentInstance)[key as string] = value
}
}

export function resolveProvided(vm: Component): Record<string, any> {
// by default an instance inherits its parent's provides object
// but when it needs to provide values of its own, it creates its
// own provides object using parent provides object as prototype.
// this way in `inject` we can simply look up injections from direct
// parent and let the prototype chain do the work.
const existing = vm._provided
const parentProvides = vm.$parent && vm.$parent._provided
if (parentProvides === existing) {
return (vm._provided = Object.create(parentProvides))
} else {
return existing
}
}

Expand Down
37 changes: 36 additions & 1 deletion test/unit/features/options/inject.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Vue from 'vue'
import { Observer } from 'core/observer/index'
import { isNative, isObject, hasOwn } from 'core/util/index'
import { isNative, isObject, hasOwn, nextTick } from 'core/util/index'
import testObjectOption from '../../../helpers/test-object-option'

describe('Options provide/inject', () => {
Expand Down Expand Up @@ -677,4 +677,39 @@ describe('Options provide/inject', () => {
})
expect(`Injection "constructor" not found`).toHaveBeenWarned()
})

// #12667
test('provide with getters', async () => {
const spy = vi.fn()
const Child = {
render() {},
inject: ['foo'],
mounted() {
spy(this.foo)
}
}

let val = 1
const vm = new Vue({
components: { Child },
template: `<Child v-if="ok" />`,
data() {
return {
ok: false
}
},
provide() {
return {
get foo() {
return val
}
}
}
}).$mount()

val = 2
vm.ok = true
await nextTick()
expect(spy).toHaveBeenCalledWith(2)
})
})

0 comments on commit ea5d0f3

Please sign in to comment.