fix(hmr): force full update in child component on slot update

This commit is contained in:
Evan You 2020-05-29 10:50:01 -04:00
parent 5ddd9d2417
commit 2408a65662
6 changed files with 24 additions and 17 deletions

View File

@ -45,7 +45,7 @@ describe('hot module replacement', () => {
const Child: ComponentOptions = { const Child: ComponentOptions = {
__hmrId: childId, __hmrId: childId,
render: compileToFunction(`<slot/>`) render: compileToFunction(`<div><slot/></div>`)
} }
createRecord(childId, Child) createRecord(childId, Child)
@ -62,13 +62,13 @@ describe('hot module replacement', () => {
createRecord(parentId, Parent) createRecord(parentId, Parent)
render(h(Parent), root) render(h(Parent), root)
expect(serializeInner(root)).toBe(`<div>00</div>`) expect(serializeInner(root)).toBe(`<div>0<div>0</div></div>`)
// Perform some state change. This change should be preserved after the // Perform some state change. This change should be preserved after the
// re-render! // re-render!
triggerEvent(root.children[0] as TestElement, 'click') triggerEvent(root.children[0] as TestElement, 'click')
await nextTick() await nextTick()
expect(serializeInner(root)).toBe(`<div>11</div>`) expect(serializeInner(root)).toBe(`<div>1<div>1</div></div>`)
// // Update text while preserving state // // Update text while preserving state
rerender( rerender(
@ -77,7 +77,7 @@ describe('hot module replacement', () => {
`<div @click="count++">{{ count }}!<Child>{{ count }}</Child></div>` `<div @click="count++">{{ count }}!<Child>{{ count }}</Child></div>`
) )
) )
expect(serializeInner(root)).toBe(`<div>1!1</div>`) expect(serializeInner(root)).toBe(`<div>1!<div>1</div></div>`)
// Should force child update on slot content change // Should force child update on slot content change
rerender( rerender(
@ -86,7 +86,7 @@ describe('hot module replacement', () => {
`<div @click="count++">{{ count }}!<Child>{{ count }}!</Child></div>` `<div @click="count++">{{ count }}!<Child>{{ count }}!</Child></div>`
) )
) )
expect(serializeInner(root)).toBe(`<div>1!1!</div>`) expect(serializeInner(root)).toBe(`<div>1!<div>1!</div></div>`)
// Should force update element children despite block optimization // Should force update element children despite block optimization
rerender( rerender(
@ -97,7 +97,7 @@ describe('hot module replacement', () => {
</div>` </div>`
) )
) )
expect(serializeInner(root)).toBe(`<div>1<span>1</span>1!</div>`) expect(serializeInner(root)).toBe(`<div>1<span>1</span><div>1!</div></div>`)
// Should force update child slot elements // Should force update child slot elements
rerender( rerender(
@ -108,7 +108,7 @@ describe('hot module replacement', () => {
</div>` </div>`
) )
) )
expect(serializeInner(root)).toBe(`<div><span>1</span></div>`) expect(serializeInner(root)).toBe(`<div><div><span>1</span></div></div>`)
}) })
test('reload', async () => { test('reload', async () => {

View File

@ -313,7 +313,7 @@ export interface ComponentInternalInstance {
* hmr marker (dev only) * hmr marker (dev only)
* @internal * @internal
*/ */
renderUpdated?: boolean hmrUpdated?: boolean
} }
const emptyAppContext = createAppContext() const emptyAppContext = createAppContext()

View File

@ -251,7 +251,7 @@ export function shouldUpdateComponent(
__DEV__ && __DEV__ &&
(prevChildren || nextChildren) && (prevChildren || nextChildren) &&
parentComponent && parentComponent &&
parentComponent.renderUpdated parentComponent.hmrUpdated
) { ) {
return true return true
} }

View File

@ -18,6 +18,7 @@ import {
import { warn } from './warning' import { warn } from './warning'
import { isKeepAlive } from './components/KeepAlive' import { isKeepAlive } from './components/KeepAlive'
import { withCtx } from './helpers/withRenderContext' import { withCtx } from './helpers/withRenderContext'
import { queuePostFlushCb } from './scheduler'
export type Slot = (...args: any[]) => VNode[] export type Slot = (...args: any[]) => VNode[]
@ -124,11 +125,17 @@ export const updateSlots = (
if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
if ((children as RawSlots)._ === 1) { if ((children as RawSlots)._ === 1) {
// compiled slots. // compiled slots.
if ( if (__DEV__ && instance.parent && instance.parent.hmrUpdated) {
// Parent was HMR updated so slot content may have changed.
// force update slots and mark instance for hmr as well
extend(slots, children as Slots)
instance.hmrUpdated = true
queuePostFlushCb(() => {
instance.hmrUpdated = false
})
} else if (
// bail on dynamic slots (v-if, v-for, reference of scope variables) // bail on dynamic slots (v-if, v-for, reference of scope variables)
!(vnode.patchFlag & PatchFlags.DYNAMIC_SLOTS) && !(vnode.patchFlag & PatchFlags.DYNAMIC_SLOTS)
// bail on HRM updates
!(__DEV__ && instance.parent && instance.parent.renderUpdated)
) { ) {
// compiled AND static. // compiled AND static.
// no need to update, and skip stale slots removal. // no need to update, and skip stale slots removal.

View File

@ -70,9 +70,9 @@ function rerender(id: string, newRender?: Function) {
} }
instance.renderCache = [] instance.renderCache = []
// this flag forces child components with slot content to update // this flag forces child components with slot content to update
instance.renderUpdated = true instance.hmrUpdated = true
instance.update() instance.update()
instance.renderUpdated = false instance.hmrUpdated = false
}) })
} }

View File

@ -779,7 +779,7 @@ function baseCreateRenderer(
invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate') invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate')
} }
if (__DEV__ && parentComponent && parentComponent.renderUpdated) { if (__DEV__ && parentComponent && parentComponent.hmrUpdated) {
// HMR updated, force full diff // HMR updated, force full diff
patchFlag = 0 patchFlag = 0
optimized = false optimized = false
@ -1006,7 +1006,7 @@ function baseCreateRenderer(
optimized = true optimized = true
} }
if (__DEV__ && parentComponent && parentComponent.renderUpdated) { if (__DEV__ && parentComponent && parentComponent.hmrUpdated) {
// HMR updated, force full diff // HMR updated, force full diff
patchFlag = 0 patchFlag = 0
optimized = false optimized = false