Skip to content

Commit

Permalink
fix(runtime-core): ensure setupContext.attrs reactivity when used in …
Browse files Browse the repository at this point in the history
…child slots

fix #4161
  • Loading branch information
yyx990803 committed Jul 21, 2021
1 parent ff0c810 commit 8560005
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 19 deletions.
38 changes: 38 additions & 0 deletions packages/runtime-core/__tests__/apiSetupContext.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down
53 changes: 34 additions & 19 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
shallowReadonly,
proxyRefs,
EffectScope,
markRaw
markRaw,
track,
TrackOpTypes
} from '@vue/reactivity'
import {
ComponentPublicInstance,
Expand Down Expand Up @@ -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(
Expand All @@ -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)
Expand All @@ -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
Expand Down

0 comments on commit 8560005

Please sign in to comment.