mirror of
https://github.com/vuejs/vue.git
synced 2024-11-21 20:28:54 +00:00
fix(inject): fix edge case of provided with async-mutated getters
fix #12667
This commit is contained in:
parent
25ffdb62d2
commit
ea5d0f3fbf
@ -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
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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', () => {
|
||||
@ -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)
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user