fix(runtime-core): ensure setupContext.attrs reactivity when used in child slots

fix #4161
This commit is contained in:
Evan You 2021-07-21 17:31:00 -04:00
parent ff0c810300
commit 8560005601
2 changed files with 72 additions and 19 deletions

View File

@ -135,6 +135,44 @@ describe('api: setup context', () => {
expect(serializeInner(root)).toMatch(`<div class="baz"></div>`)
})
// #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(`<div id="foo"></div>`)
// should update even though it's not reactive
toggle.value = false
await nextTick()
expect(serializeInner(root)).toMatch(`<div class="baz"></div>`)
})
it('context.slots', async () => {
const id = ref('foo')

View File

@ -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<Data> = {
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