fix(runtime-core): ensure setupContext.attrs reactivity when used in child slots
fix #4161
This commit is contained in:
parent
ff0c810300
commit
8560005601
@ -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')
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user