mirror of https://github.com/vuejs/core.git
parent
9b5a34bf8c
commit
94fa67a4f7
|
@ -537,4 +537,35 @@ describe('hot module replacement', () => {
|
||||||
render(h(Foo), root)
|
render(h(Foo), root)
|
||||||
expect(serializeInner(root)).toBe('bar')
|
expect(serializeInner(root)).toBe('bar')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #7155 - force HMR on slots content update
|
||||||
|
test('force update slot content change', () => {
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
const parentId = 'test-force-computed-parent'
|
||||||
|
const childId = 'test-force-computed-child'
|
||||||
|
|
||||||
|
const Child: ComponentOptions = {
|
||||||
|
__hmrId: childId,
|
||||||
|
computed: {
|
||||||
|
slotContent() {
|
||||||
|
return this.$slots.default?.()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: compileToFunction(`<component :is="() => slotContent" />`)
|
||||||
|
}
|
||||||
|
createRecord(childId, Child)
|
||||||
|
|
||||||
|
const Parent: ComponentOptions = {
|
||||||
|
__hmrId: parentId,
|
||||||
|
components: { Child },
|
||||||
|
render: compileToFunction(`<Child>1</Child>`)
|
||||||
|
}
|
||||||
|
createRecord(parentId, Parent)
|
||||||
|
|
||||||
|
render(h(Parent), root)
|
||||||
|
expect(serializeInner(root)).toBe(`1`)
|
||||||
|
|
||||||
|
rerender(parentId, compileToFunction(`<Child>2</Child>`))
|
||||||
|
expect(serializeInner(root)).toBe(`2`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -349,6 +349,10 @@ export interface ComponentInternalInstance {
|
||||||
slots: InternalSlots
|
slots: InternalSlots
|
||||||
refs: Data
|
refs: Data
|
||||||
emit: EmitFn
|
emit: EmitFn
|
||||||
|
|
||||||
|
attrsProxy: Data | null
|
||||||
|
slotsProxy: Slots | null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* used for keeping track of .once event handlers on components
|
* used for keeping track of .once event handlers on components
|
||||||
* @internal
|
* @internal
|
||||||
|
@ -536,6 +540,9 @@ export function createComponentInstance(
|
||||||
setupState: EMPTY_OBJ,
|
setupState: EMPTY_OBJ,
|
||||||
setupContext: null,
|
setupContext: null,
|
||||||
|
|
||||||
|
attrsProxy: null,
|
||||||
|
slotsProxy: null,
|
||||||
|
|
||||||
// suspense related
|
// suspense related
|
||||||
suspense,
|
suspense,
|
||||||
suspenseId: suspense ? suspense.pendingId : 0,
|
suspenseId: suspense ? suspense.pendingId : 0,
|
||||||
|
@ -923,31 +930,57 @@ export function finishComponentSetup(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAttrsProxy(instance: ComponentInternalInstance): Data {
|
function getAttrsProxy(instance: ComponentInternalInstance): Data {
|
||||||
return new Proxy(
|
return (
|
||||||
instance.attrs,
|
instance.attrsProxy ||
|
||||||
__DEV__
|
(instance.attrsProxy = new Proxy(
|
||||||
? {
|
instance.attrs,
|
||||||
get(target, key: string) {
|
__DEV__
|
||||||
markAttrsAccessed()
|
? {
|
||||||
track(instance, TrackOpTypes.GET, '$attrs')
|
get(target, key: string) {
|
||||||
return target[key]
|
markAttrsAccessed()
|
||||||
},
|
track(instance, TrackOpTypes.GET, '$attrs')
|
||||||
set() {
|
return target[key]
|
||||||
warn(`setupContext.attrs is readonly.`)
|
},
|
||||||
return false
|
set() {
|
||||||
},
|
warn(`setupContext.attrs is readonly.`)
|
||||||
deleteProperty() {
|
return false
|
||||||
warn(`setupContext.attrs is readonly.`)
|
},
|
||||||
return false
|
deleteProperty() {
|
||||||
|
warn(`setupContext.attrs is readonly.`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
: {
|
||||||
: {
|
get(target, key: string) {
|
||||||
get(target, key: string) {
|
track(instance, TrackOpTypes.GET, '$attrs')
|
||||||
track(instance, TrackOpTypes.GET, '$attrs')
|
return target[key]
|
||||||
return target[key]
|
}
|
||||||
}
|
}
|
||||||
}
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dev-only
|
||||||
|
*/
|
||||||
|
function getSlotsProxy(instance: ComponentInternalInstance): Slots {
|
||||||
|
return (
|
||||||
|
instance.slotsProxy ||
|
||||||
|
(instance.slotsProxy = new Proxy(instance.slots, {
|
||||||
|
get(target, key: string) {
|
||||||
|
track(instance, TrackOpTypes.GET, '$slots')
|
||||||
|
return target[key]
|
||||||
|
},
|
||||||
|
set() {
|
||||||
|
warn(`setupContext.slots is readonly.`)
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
deleteProperty() {
|
||||||
|
warn(`setupContext.slots is readonly.`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -978,16 +1011,15 @@ export function createSetupContext(
|
||||||
instance.exposed = exposed || {}
|
instance.exposed = exposed || {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let attrs: Data
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
// We use getters in dev in case libs like test-utils overwrite instance
|
// We use getters in dev in case libs like test-utils overwrite instance
|
||||||
// properties (overwrites should not be done in prod)
|
// properties (overwrites should not be done in prod)
|
||||||
return Object.freeze({
|
return Object.freeze({
|
||||||
get attrs() {
|
get attrs() {
|
||||||
return attrs || (attrs = createAttrsProxy(instance))
|
return getAttrsProxy(instance)
|
||||||
},
|
},
|
||||||
get slots() {
|
get slots() {
|
||||||
return shallowReadonly(instance.slots)
|
return getSlotsProxy(instance)
|
||||||
},
|
},
|
||||||
get emit() {
|
get emit() {
|
||||||
return (event: string, ...args: any[]) => instance.emit(event, ...args)
|
return (event: string, ...args: any[]) => instance.emit(event, ...args)
|
||||||
|
@ -997,7 +1029,7 @@ export function createSetupContext(
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
get attrs() {
|
get attrs() {
|
||||||
return attrs || (attrs = createAttrsProxy(instance))
|
return getAttrsProxy(instance)
|
||||||
},
|
},
|
||||||
slots: instance.slots,
|
slots: instance.slots,
|
||||||
emit: instance.emit,
|
emit: instance.emit,
|
||||||
|
|
|
@ -356,6 +356,8 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
||||||
if (key === '$attrs') {
|
if (key === '$attrs') {
|
||||||
track(instance, TrackOpTypes.GET, key)
|
track(instance, TrackOpTypes.GET, key)
|
||||||
__DEV__ && markAttrsAccessed()
|
__DEV__ && markAttrsAccessed()
|
||||||
|
} else if (__DEV__ && key === '$slots') {
|
||||||
|
track(instance, TrackOpTypes.GET, key)
|
||||||
}
|
}
|
||||||
return publicGetter(instance)
|
return publicGetter(instance)
|
||||||
} else if (
|
} else if (
|
||||||
|
|
|
@ -23,6 +23,8 @@ import { ContextualRenderFn, withCtx } from './componentRenderContext'
|
||||||
import { isHmrUpdating } from './hmr'
|
import { isHmrUpdating } from './hmr'
|
||||||
import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
|
import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
|
||||||
import { toRaw } from '@vue/reactivity'
|
import { toRaw } from '@vue/reactivity'
|
||||||
|
import { trigger } from '@vue/reactivity'
|
||||||
|
import { TriggerOpTypes } from '@vue/reactivity'
|
||||||
|
|
||||||
export type Slot<T extends any = any> = (
|
export type Slot<T extends any = any> = (
|
||||||
...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
|
...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
|
||||||
|
@ -196,6 +198,7 @@ export const updateSlots = (
|
||||||
// Parent was HMR updated so slot content may have changed.
|
// Parent was HMR updated so slot content may have changed.
|
||||||
// force update slots and mark instance for hmr as well
|
// force update slots and mark instance for hmr as well
|
||||||
extend(slots, children as Slots)
|
extend(slots, children as Slots)
|
||||||
|
trigger(instance, TriggerOpTypes.SET, '$slots')
|
||||||
} else if (optimized && type === SlotFlags.STABLE) {
|
} else if (optimized && type === SlotFlags.STABLE) {
|
||||||
// compiled AND stable.
|
// compiled AND stable.
|
||||||
// no need to update, and skip stale slots removal.
|
// no need to update, and skip stale slots removal.
|
||||||
|
|
Loading…
Reference in New Issue