From: Evan You Date: Wed, 21 Jul 2021 21:31:00 +0000 (-0400) Subject: fix(runtime-core): ensure setupContext.attrs reactivity when used in child slots X-Git-Tag: v3.2.0-beta.4~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=85600056015fcf5c922dc0b5b07aa03a5ba53245;p=thirdparty%2Fvuejs%2Fcore.git fix(runtime-core): ensure setupContext.attrs reactivity when used in child slots fix #4161 --- diff --git a/packages/runtime-core/__tests__/apiSetupContext.spec.ts b/packages/runtime-core/__tests__/apiSetupContext.spec.ts index 3933c6f112..eea75ac93a 100644 --- a/packages/runtime-core/__tests__/apiSetupContext.spec.ts +++ b/packages/runtime-core/__tests__/apiSetupContext.spec.ts @@ -135,6 +135,44 @@ describe('api: setup context', () => { expect(serializeInner(root)).toMatch(`
`) }) + // #4161 + it('context.attrs in child component slots', async () => { + const toggle = ref(true) + + const Parent = { + render: () => h(Child, toggle.value ? { id: 'foo' } : { class: 'baz' }) + } + + const Wrapper = { + render(this: any) { + return this.$slots.default() + } + } + + const Child = { + inheritAttrs: false, + setup(_: any, { attrs }: any) { + return () => { + const vnode = h(Wrapper, null, { + default: () => [h('div', attrs)], + _: 1 // mark stable slots + }) + vnode.dynamicChildren = [] // force optimized mode + return vnode + } + } + } + + const root = nodeOps.createElement('div') + render(h(Parent), root) + expect(serializeInner(root)).toMatch(`
`) + + // should update even though it's not reactive + toggle.value = false + await nextTick() + expect(serializeInner(root)).toMatch(`
`) + }) + it('context.slots', async () => { const id = ref('foo') diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 8c1d55a216..86c89c5988 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -5,7 +5,9 @@ import { shallowReadonly, proxyRefs, EffectScope, - markRaw + markRaw, + track, + TrackOpTypes } from '@vue/reactivity' import { ComponentPublicInstance, @@ -834,19 +836,32 @@ export function finishComponentSetup( } } -const attrDevProxyHandlers: ProxyHandler = { - get: (target, key: string) => { - markAttrsAccessed() - return target[key] - }, - set: () => { - warn(`setupContext.attrs is readonly.`) - return false - }, - deleteProperty: () => { - warn(`setupContext.attrs is readonly.`) - return false - } +function createAttrsProxy(instance: ComponentInternalInstance): Data { + return new Proxy( + instance.attrs, + __DEV__ + ? { + get(target, key: string) { + markAttrsAccessed() + track(instance, TrackOpTypes.GET, '$attrs') + return target[key] + }, + set() { + warn(`setupContext.attrs is readonly.`) + return false + }, + deleteProperty() { + warn(`setupContext.attrs is readonly.`) + return false + } + } + : { + get(target, key: string) { + track(instance, TrackOpTypes.GET, '$attrs') + return target[key] + } + } + ) } export function createSetupContext( @@ -859,15 +874,13 @@ export function createSetupContext( instance.exposed = exposed || {} } + let attrs: Data if (__DEV__) { - let attrs: Data // We use getters in dev in case libs like test-utils overwrite instance // properties (overwrites should not be done in prod) return Object.freeze({ get attrs() { - return ( - attrs || (attrs = new Proxy(instance.attrs, attrDevProxyHandlers)) - ) + return attrs || (attrs = createAttrsProxy(instance)) }, get slots() { return shallowReadonly(instance.slots) @@ -879,7 +892,9 @@ export function createSetupContext( }) } else { return { - attrs: instance.attrs, + get attrs() { + return attrs || (attrs = createAttrsProxy(instance)) + }, slots: instance.slots, emit: instance.emit, expose