Skip to content

Commit 561e210

Browse files
committed
fix(inject): should auto unwrap injected refs
fix #4196
1 parent 8681c12 commit 561e210

File tree

4 files changed

+119
-15
lines changed

4 files changed

+119
-15
lines changed

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

+65-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
renderToString,
1010
ref,
1111
defineComponent,
12-
createApp
12+
createApp,
13+
computed
1314
} from '@vue/runtime-test'
1415

1516
describe('api: options', () => {
@@ -426,6 +427,69 @@ describe('api: options', () => {
426427
expect(renderToString(h(Root))).toBe(`1111234522`)
427428
})
428429

430+
test('provide/inject refs', async () => {
431+
const n = ref(0)
432+
const np = computed(() => n.value + 1)
433+
const Parent = defineComponent({
434+
provide() {
435+
return {
436+
n,
437+
np
438+
}
439+
},
440+
render: () => h(Child)
441+
})
442+
const Child = defineComponent({
443+
inject: ['n', 'np'],
444+
render(this: any) {
445+
return this.n + this.np
446+
}
447+
})
448+
const app = createApp(Parent)
449+
// TODO remove in 3.3
450+
app.config.unwrapInjectedRef = true
451+
const root = nodeOps.createElement('div')
452+
app.mount(root)
453+
expect(serializeInner(root)).toBe(`1`)
454+
455+
n.value++
456+
await nextTick()
457+
expect(serializeInner(root)).toBe(`3`)
458+
})
459+
460+
// TODO remove in 3.3
461+
test('provide/inject refs (compat)', async () => {
462+
const n = ref(0)
463+
const np = computed(() => n.value + 1)
464+
const Parent = defineComponent({
465+
provide() {
466+
return {
467+
n,
468+
np
469+
}
470+
},
471+
render: () => h(Child)
472+
})
473+
const Child = defineComponent({
474+
inject: ['n', 'np'],
475+
render(this: any) {
476+
return this.n.value + this.np.value
477+
}
478+
})
479+
const app = createApp(Parent)
480+
481+
const root = nodeOps.createElement('div')
482+
app.mount(root)
483+
expect(serializeInner(root)).toBe(`1`)
484+
485+
n.value++
486+
await nextTick()
487+
expect(serializeInner(root)).toBe(`3`)
488+
489+
expect(`injected property "n" is a ref`).toHaveBeenWarned()
490+
expect(`injected property "np" is a ref`).toHaveBeenWarned()
491+
})
492+
429493
test('provide accessing data in extends', () => {
430494
const Base = defineComponent({
431495
data() {

packages/runtime-core/src/apiCreateApp.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,22 @@ export interface AppConfig {
8181
trace: string
8282
) => void
8383

84+
/**
85+
* Options to pass to @vue/compiler-dom.
86+
* Only supported in runtime compiler build.
87+
*/
88+
compilerOptions: RuntimeCompilerOptions
89+
8490
/**
8591
* @deprecated use config.compilerOptions.isCustomElement
8692
*/
8793
isCustomElement?: (tag: string) => boolean
8894

8995
/**
90-
* Options to pass to @vue/compiler-dom.
91-
* Only supported in runtime compiler build.
96+
* Temporary config for opt-in to unwrap injected refs.
97+
* TODO deprecate in 3.3
9298
*/
93-
compilerOptions: RuntimeCompilerOptions
99+
unwrapInjectedRef?: boolean
94100
}
95101

96102
export interface AppContext {

packages/runtime-core/src/componentOptions.ts

+40-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
NOOP,
1818
isPromise
1919
} from '@vue/shared'
20-
import { computed } from '@vue/reactivity'
20+
import { computed, isRef, Ref } from '@vue/reactivity'
2121
import {
2222
watch,
2323
WatchOptions,
@@ -607,15 +607,21 @@ export function applyOptions(instance: ComponentInternalInstance) {
607607
// - watch (deferred since it relies on `this` access)
608608

609609
if (injectOptions) {
610-
resolveInjections(injectOptions, ctx, checkDuplicateProperties)
610+
resolveInjections(
611+
injectOptions,
612+
ctx,
613+
checkDuplicateProperties,
614+
instance.appContext.config.unwrapInjectedRef
615+
)
611616
}
612617

613618
if (methods) {
614619
for (const key in methods) {
615620
const methodHandler = (methods as MethodOptions)[key]
616621
if (isFunction(methodHandler)) {
617-
// In dev mode, we use the `createRenderContext` function to define methods to the proxy target,
618-
// and those are read-only but reconfigurable, so it needs to be redefined here
622+
// In dev mode, we use the `createRenderContext` function to define
623+
// methods to the proxy target, and those are read-only but
624+
// reconfigurable, so it needs to be redefined here
619625
if (__DEV__) {
620626
Object.defineProperty(ctx, key, {
621627
value: methodHandler.bind(publicThis),
@@ -810,25 +816,51 @@ export function applyOptions(instance: ComponentInternalInstance) {
810816
export function resolveInjections(
811817
injectOptions: ComponentInjectOptions,
812818
ctx: any,
813-
checkDuplicateProperties = NOOP as any
819+
checkDuplicateProperties = NOOP as any,
820+
unwrapRef = false
814821
) {
815822
if (isArray(injectOptions)) {
816823
injectOptions = normalizeInject(injectOptions)!
817824
}
818825
for (const key in injectOptions) {
819826
const opt = (injectOptions as ObjectInjectOptions)[key]
827+
let injected: unknown
820828
if (isObject(opt)) {
821829
if ('default' in opt) {
822-
ctx[key] = inject(
830+
injected = inject(
823831
opt.from || key,
824832
opt.default,
825833
true /* treat default function as factory */
826834
)
827835
} else {
828-
ctx[key] = inject(opt.from || key)
836+
injected = inject(opt.from || key)
837+
}
838+
} else {
839+
injected = inject(opt)
840+
}
841+
if (isRef(injected)) {
842+
// TODO remove the check in 3.3
843+
if (unwrapRef) {
844+
Object.defineProperty(ctx, key, {
845+
enumerable: true,
846+
configurable: true,
847+
get: () => (injected as Ref).value,
848+
set: v => ((injected as Ref).value = v)
849+
})
850+
} else {
851+
if (__DEV__) {
852+
warn(
853+
`injected property "${key}" is a ref and will be auto-unwrapped ` +
854+
`and no longer needs \`.value\` in the next minor release. ` +
855+
`To opt-in to the new behavior now, ` +
856+
`set \`app.config.unwrapInjectedRef = true\` (this config is ` +
857+
`temporary and will not be needed in the future.)`
858+
)
859+
}
860+
ctx[key] = injected
829861
}
830862
} else {
831-
ctx[key] = inject(opt)
863+
ctx[key] = injected
832864
}
833865
if (__DEV__) {
834866
checkDuplicateProperties!(OptionTypes.INJECT, key)

packages/runtime-dom/__tests__/customElement.spec.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import {
22
defineCustomElement,
33
h,
4+
inject,
45
nextTick,
6+
Ref,
57
ref,
68
renderSlot,
79
VueElement
@@ -231,9 +233,9 @@ describe('defineCustomElement', () => {
231233

232234
describe('provide/inject', () => {
233235
const Consumer = defineCustomElement({
234-
inject: ['foo'],
235-
render(this: any) {
236-
return h('div', this.foo.value)
236+
setup() {
237+
const foo = inject<Ref>('foo')!
238+
return () => h('div', foo.value)
237239
}
238240
})
239241
customElements.define('my-consumer', Consumer)

0 commit comments

Comments
 (0)