Skip to content

Commit

Permalink
fix(inject): should auto unwrap injected refs
Browse files Browse the repository at this point in the history
fix #4196
  • Loading branch information
yyx990803 committed Jul 27, 2021
1 parent 8681c12 commit 561e210
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 15 deletions.
66 changes: 65 additions & 1 deletion packages/runtime-core/__tests__/apiOptions.spec.ts
Expand Up @@ -9,7 +9,8 @@ import {
renderToString,
ref,
defineComponent,
createApp
createApp,
computed
} from '@vue/runtime-test'

describe('api: options', () => {
Expand Down Expand Up @@ -426,6 +427,69 @@ describe('api: options', () => {
expect(renderToString(h(Root))).toBe(`1111234522`)
})

test('provide/inject refs', async () => {
const n = ref(0)
const np = computed(() => n.value + 1)
const Parent = defineComponent({
provide() {
return {
n,
np
}
},
render: () => h(Child)
})
const Child = defineComponent({
inject: ['n', 'np'],
render(this: any) {
return this.n + this.np
}
})
const app = createApp(Parent)
// TODO remove in 3.3
app.config.unwrapInjectedRef = true
const root = nodeOps.createElement('div')
app.mount(root)
expect(serializeInner(root)).toBe(`1`)

n.value++
await nextTick()
expect(serializeInner(root)).toBe(`3`)
})

// TODO remove in 3.3
test('provide/inject refs (compat)', async () => {
const n = ref(0)
const np = computed(() => n.value + 1)
const Parent = defineComponent({
provide() {
return {
n,
np
}
},
render: () => h(Child)
})
const Child = defineComponent({
inject: ['n', 'np'],
render(this: any) {
return this.n.value + this.np.value
}
})
const app = createApp(Parent)

const root = nodeOps.createElement('div')
app.mount(root)
expect(serializeInner(root)).toBe(`1`)

n.value++
await nextTick()
expect(serializeInner(root)).toBe(`3`)

expect(`injected property "n" is a ref`).toHaveBeenWarned()
expect(`injected property "np" is a ref`).toHaveBeenWarned()
})

test('provide accessing data in extends', () => {
const Base = defineComponent({
data() {
Expand Down
12 changes: 9 additions & 3 deletions packages/runtime-core/src/apiCreateApp.ts
Expand Up @@ -81,16 +81,22 @@ export interface AppConfig {
trace: string
) => void

/**
* Options to pass to @vue/compiler-dom.
* Only supported in runtime compiler build.
*/
compilerOptions: RuntimeCompilerOptions

/**
* @deprecated use config.compilerOptions.isCustomElement
*/
isCustomElement?: (tag: string) => boolean

/**
* Options to pass to @vue/compiler-dom.
* Only supported in runtime compiler build.
* Temporary config for opt-in to unwrap injected refs.
* TODO deprecate in 3.3
*/
compilerOptions: RuntimeCompilerOptions
unwrapInjectedRef?: boolean
}

export interface AppContext {
Expand Down
48 changes: 40 additions & 8 deletions packages/runtime-core/src/componentOptions.ts
Expand Up @@ -17,7 +17,7 @@ import {
NOOP,
isPromise
} from '@vue/shared'
import { computed } from '@vue/reactivity'
import { computed, isRef, Ref } from '@vue/reactivity'
import {
watch,
WatchOptions,
Expand Down Expand Up @@ -607,15 +607,21 @@ export function applyOptions(instance: ComponentInternalInstance) {
// - watch (deferred since it relies on `this` access)

if (injectOptions) {
resolveInjections(injectOptions, ctx, checkDuplicateProperties)
resolveInjections(
injectOptions,
ctx,
checkDuplicateProperties,
instance.appContext.config.unwrapInjectedRef
)
}

if (methods) {
for (const key in methods) {
const methodHandler = (methods as MethodOptions)[key]
if (isFunction(methodHandler)) {
// In dev mode, we use the `createRenderContext` function to define methods to the proxy target,
// and those are read-only but reconfigurable, so it needs to be redefined here
// In dev mode, we use the `createRenderContext` function to define
// methods to the proxy target, and those are read-only but
// reconfigurable, so it needs to be redefined here
if (__DEV__) {
Object.defineProperty(ctx, key, {
value: methodHandler.bind(publicThis),
Expand Down Expand Up @@ -810,25 +816,51 @@ export function applyOptions(instance: ComponentInternalInstance) {
export function resolveInjections(
injectOptions: ComponentInjectOptions,
ctx: any,
checkDuplicateProperties = NOOP as any
checkDuplicateProperties = NOOP as any,
unwrapRef = false
) {
if (isArray(injectOptions)) {
injectOptions = normalizeInject(injectOptions)!
}
for (const key in injectOptions) {
const opt = (injectOptions as ObjectInjectOptions)[key]
let injected: unknown
if (isObject(opt)) {
if ('default' in opt) {
ctx[key] = inject(
injected = inject(
opt.from || key,
opt.default,
true /* treat default function as factory */
)
} else {
ctx[key] = inject(opt.from || key)
injected = inject(opt.from || key)
}
} else {
injected = inject(opt)
}
if (isRef(injected)) {
// TODO remove the check in 3.3
if (unwrapRef) {
Object.defineProperty(ctx, key, {
enumerable: true,
configurable: true,
get: () => (injected as Ref).value,
set: v => ((injected as Ref).value = v)
})
} else {
if (__DEV__) {
warn(
`injected property "${key}" is a ref and will be auto-unwrapped ` +
`and no longer needs \`.value\` in the next minor release. ` +
`To opt-in to the new behavior now, ` +
`set \`app.config.unwrapInjectedRef = true\` (this config is ` +
`temporary and will not be needed in the future.)`
)
}
ctx[key] = injected
}
} else {
ctx[key] = inject(opt)
ctx[key] = injected
}
if (__DEV__) {
checkDuplicateProperties!(OptionTypes.INJECT, key)
Expand Down
8 changes: 5 additions & 3 deletions packages/runtime-dom/__tests__/customElement.spec.ts
@@ -1,7 +1,9 @@
import {
defineCustomElement,
h,
inject,
nextTick,
Ref,
ref,
renderSlot,
VueElement
Expand Down Expand Up @@ -231,9 +233,9 @@ describe('defineCustomElement', () => {

describe('provide/inject', () => {
const Consumer = defineCustomElement({
inject: ['foo'],
render(this: any) {
return h('div', this.foo.value)
setup() {
const foo = inject<Ref>('foo')!
return () => h('div', foo.value)
}
})
customElements.define('my-consumer', Consumer)
Expand Down

0 comments on commit 561e210

Please sign in to comment.