diff --git a/src/mixin.ts b/src/mixin.ts index e560796c..54f0c21e 100644 --- a/src/mixin.ts +++ b/src/mixin.ts @@ -34,7 +34,7 @@ export function mixin(Vue: VueConstructor) { updateTemplateRef(this) }, beforeUpdate() { - updateVmAttrs(this as ComponentInstance, this as SetupContext) + updateVmAttrs(this as ComponentInstance) }, updated(this: ComponentInstance) { updateTemplateRef(this) diff --git a/src/utils/instance.ts b/src/utils/instance.ts index 47d135fe..5544f976 100644 --- a/src/utils/instance.ts +++ b/src/utils/instance.ts @@ -103,18 +103,22 @@ export function updateTemplateRef(vm: ComponentInstance) { vmStateManager.set(vm, 'refs', validNewKeys) } -export function updateVmAttrs(vm: ComponentInstance, ctx: SetupContext) { - if (!vm || !ctx) { +export function updateVmAttrs(vm: ComponentInstance, ctx?: SetupContext) { + if (!vm) { return } let attrBindings = vmStateManager.get(vm, 'attrBindings') + if (!attrBindings && !ctx) { + // fix 840 + return + } if (!attrBindings) { const observedData = reactive({}) - vmStateManager.set(vm, 'attrBindings', observedData) - attrBindings = observedData + attrBindings = { ctx: ctx!, data: observedData } + vmStateManager.set(vm, 'attrBindings', attrBindings) proxy(ctx, 'attrs', { get: () => { - return attrBindings + return attrBindings?.data }, set() { __DEV__ && @@ -128,8 +132,8 @@ export function updateVmAttrs(vm: ComponentInstance, ctx: SetupContext) { const source = vm.$attrs for (const attr of Object.keys(source)) { - if (!hasOwn(attrBindings!, attr)) { - proxy(attrBindings, attr, { + if (!hasOwn(attrBindings.data, attr)) { + proxy(attrBindings.data, attr, { get: () => { // to ensure it always return the latest value return vm.$attrs[attr] diff --git a/src/utils/vmStateManager.ts b/src/utils/vmStateManager.ts index 5a9d80f4..38963352 100644 --- a/src/utils/vmStateManager.ts +++ b/src/utils/vmStateManager.ts @@ -1,9 +1,13 @@ import { ComponentInstance, Data } from '../component' +import { SetupContext } from '../runtimeContext' export interface VfaState { refs?: string[] rawBindings?: Data - attrBindings?: Data + attrBindings?: { + ctx: SetupContext + data: Data + } slots?: string[] } diff --git a/test/setup.spec.js b/test/setup.spec.js index 5ae0e6be..4f55b03d 100644 --- a/test/setup.spec.js +++ b/test/setup.spec.js @@ -1304,4 +1304,81 @@ describe('setup', () => { lastName: 'xiao', }) }) + + // #840 + it('changing prop causes rerender to lose attributes', async () => { + let childAttrs = [] + const Parent = { + computed: { + attrs() { + return { + 'data-type': this.type, + } + }, + }, + props: { + type: { + type: String, + required: true, + }, + }, + mounted() { + childAttrs.push(this.attrs) + }, + updated() { + childAttrs.push(this.attrs) + }, + template: '
Parent
', + } + const Child = { + props: { + type: { + type: String, + required: true, + }, + }, + computed: { + attrs() { + return { + 'data-type': this.type, + } + }, + }, + data() { + return { + update: 0, + } + }, + template: '
Child
', + } + + const App = { + name: 'App', + data() { + return { parentType: 'parent' } + }, + async mounted() { + await sleep(300) + this.parentType = 'parent-click' + }, + + components: { + Parent, + Child, + }, + template: `
+ + + +
`, + } + const vm = new Vue(App).$mount() + + await sleep(1000) + await vm.$nextTick() + expect(childAttrs).toStrictEqual([ + { 'data-type': 'parent' }, + { 'data-type': 'parent-click' }, + ]) + }) })