Skip to content

Commit 8f2a748

Browse files
authored
fix(hmr): force full update on nested child components (#1312)
1 parent 4492b88 commit 8f2a748

File tree

3 files changed

+94
-13
lines changed

3 files changed

+94
-13
lines changed

packages/runtime-core/__tests__/hmr.spec.ts

+71
Original file line numberDiff line numberDiff line change
@@ -218,4 +218,75 @@ describe('hot module replacement', () => {
218218
rerender(parentId, compileToFunction(`<Child msg="bar" />`))
219219
expect(serializeInner(root)).toBe(`<div>bar</div>`)
220220
})
221+
222+
// #1305 - component should remove class
223+
test('remove static class from parent', () => {
224+
const root = nodeOps.createElement('div')
225+
const parentId = 'test-force-class-parent'
226+
const childId = 'test-force-class-child'
227+
228+
const Child: ComponentOptions = {
229+
__hmrId: childId,
230+
render: compileToFunction(`<div>child</div>`)
231+
}
232+
createRecord(childId, Child)
233+
234+
const Parent: ComponentOptions = {
235+
__hmrId: parentId,
236+
components: { Child },
237+
render: compileToFunction(`<Child class="test" />`)
238+
}
239+
createRecord(parentId, Parent)
240+
241+
render(h(Parent), root)
242+
expect(serializeInner(root)).toBe(`<div class="test">child</div>`)
243+
244+
rerender(parentId, compileToFunction(`<Child/>`))
245+
expect(serializeInner(root)).toBe(`<div>child</div>`)
246+
})
247+
248+
test('rerender if any parent in the parent chain', () => {
249+
const root = nodeOps.createElement('div')
250+
const parent = 'test-force-props-parent-'
251+
const childId = 'test-force-props-child'
252+
253+
const numberOfParents = 5
254+
255+
const Child: ComponentOptions = {
256+
__hmrId: childId,
257+
render: compileToFunction(`<div>child</div>`)
258+
}
259+
createRecord(childId, Child)
260+
261+
const components: ComponentOptions[] = []
262+
263+
for (let i = 0; i < numberOfParents; i++) {
264+
const parentId = `${parent}${i}`
265+
const parentComp: ComponentOptions = {
266+
__hmrId: parentId
267+
}
268+
components.push(parentComp)
269+
if (i === 0) {
270+
parentComp.render = compileToFunction(`<Child />`)
271+
parentComp.components = {
272+
Child
273+
}
274+
} else {
275+
parentComp.render = compileToFunction(`<Parent />`)
276+
parentComp.components = {
277+
Parent: components[i - 1]
278+
}
279+
}
280+
281+
createRecord(parentId, parentComp)
282+
}
283+
284+
const last = components[components.length - 1]
285+
286+
render(h(last), root)
287+
expect(serializeInner(root)).toBe(`<div>child</div>`)
288+
289+
rerender(last.__hmrId!, compileToFunction(`<Parent class="test"/>`))
290+
expect(serializeInner(root)).toBe(`<div class="test">child</div>`)
291+
})
221292
})

packages/runtime-core/src/componentRenderUtils.ts

+11-8
Original file line numberDiff line numberDiff line change
@@ -247,13 +247,13 @@ export function shouldUpdateComponent(
247247
// Parent component's render function was hot-updated. Since this may have
248248
// caused the child component's slots content to have changed, we need to
249249
// force the child to update as well.
250-
if (
251-
__DEV__ &&
252-
(prevChildren || nextChildren) &&
253-
parentComponent &&
254-
parentComponent.hmrUpdated
255-
) {
256-
return true
250+
if (__DEV__ && (prevChildren || nextChildren) && parentComponent) {
251+
let parent: ComponentInternalInstance | null = parentComponent
252+
do {
253+
if (parent.hmrUpdated) {
254+
return true
255+
}
256+
} while ((parent = parent.parent))
257257
}
258258

259259
// force child update for runtime directive or transition on component vnode.
@@ -268,8 +268,11 @@ export function shouldUpdateComponent(
268268
return true
269269
}
270270
if (patchFlag & PatchFlags.FULL_PROPS) {
271+
if (!prevProps) {
272+
return !!nextProps
273+
}
271274
// presence of this flag indicates props are always non-null
272-
return hasPropsChanged(prevProps!, nextProps!)
275+
return hasPropsChanged(prevProps, nextProps!)
273276
} else if (patchFlag & PatchFlags.PROPS) {
274277
const dynamicProps = nextVNode.dynamicProps!
275278
for (let i = 0; i < dynamicProps.length; i++) {

packages/runtime-core/src/renderer.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -791,11 +791,18 @@ function baseCreateRenderer(
791791
invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate')
792792
}
793793

794-
if (__DEV__ && parentComponent && parentComponent.hmrUpdated) {
795-
// HMR updated, force full diff
796-
patchFlag = 0
797-
optimized = false
798-
dynamicChildren = null
794+
// check if any component of the parent chain has `hmrUpdated`
795+
if (__DEV__ && parentComponent) {
796+
let parent: ComponentInternalInstance | null = parentComponent
797+
do {
798+
if (parent.hmrUpdated) {
799+
// HMR updated, force full diff
800+
patchFlag = 0
801+
optimized = false
802+
dynamicChildren = null
803+
break
804+
}
805+
} while ((parent = parent.parent))
799806
}
800807

801808
if (patchFlag > 0) {

0 commit comments

Comments
 (0)