Skip to content

Commit 57866b5

Browse files
authored
fix(runtime-core): prevent child component updates when style remains unchanged (#12825)
close #12826
1 parent b0a1f05 commit 57866b5

2 files changed

Lines changed: 75 additions & 3 deletions

File tree

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
inject,
77
nextTick,
88
nodeOps,
9+
onMounted,
910
provide,
1011
ref,
1112
render,
@@ -474,4 +475,55 @@ describe('renderer: component', () => {
474475
`Property '$attrs' was accessed via 'this'. Avoid using 'this' in templates.`,
475476
).toHaveBeenWarned()
476477
})
478+
479+
test('should not update child component if style is not changed', async () => {
480+
const text = ref(0)
481+
const spy = vi.fn()
482+
483+
const ClientOnly = {
484+
setup(_: any, { slots }: SetupContext) {
485+
const mounted = ref(false)
486+
onMounted(() => {
487+
mounted.value = true
488+
})
489+
return () => {
490+
if (mounted.value) {
491+
return slots.default!()
492+
}
493+
}
494+
},
495+
}
496+
497+
const App = {
498+
render() {
499+
return h(ClientOnly, null, {
500+
default: () => [
501+
h('span', null, [text.value]),
502+
h(Comp, { style: { width: '100%' } }),
503+
],
504+
})
505+
},
506+
}
507+
508+
const Comp = {
509+
render(this: any) {
510+
spy()
511+
return null
512+
},
513+
}
514+
515+
const root = nodeOps.createElement('div')
516+
render(h(App), root)
517+
expect(serializeInner(root)).toBe(`<!---->`)
518+
await nextTick()
519+
520+
expect(serializeInner(root)).toBe(`<span>0</span><!---->`)
521+
expect(spy).toHaveBeenCalledTimes(1)
522+
523+
text.value++
524+
await nextTick()
525+
expect(serializeInner(root)).toBe(`<span>1</span><!---->`)
526+
// expect Comp to not be re-rendered
527+
expect(spy).toHaveBeenCalledTimes(1)
528+
})
477529
})

packages/runtime-core/src/componentRenderUtils.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,14 @@ import {
1515
normalizeVNode,
1616
} from './vnode'
1717
import { ErrorCodes, handleError } from './errorHandling'
18-
import { PatchFlags, ShapeFlags, isModelListener, isOn } from '@vue/shared'
18+
import {
19+
PatchFlags,
20+
ShapeFlags,
21+
isModelListener,
22+
isObject,
23+
isOn,
24+
looseEqual,
25+
} from '@vue/shared'
1926
import { warn } from './warning'
2027
import { isHmrUpdating } from './hmr'
2128
import type { NormalizedProps } from './componentProps'
@@ -399,7 +406,7 @@ export function shouldUpdateComponent(
399406
for (let i = 0; i < dynamicProps.length; i++) {
400407
const key = dynamicProps[i]
401408
if (
402-
nextProps![key] !== prevProps![key] &&
409+
hasPropValueChanged(nextProps!, prevProps!, key) &&
403410
!isEmitListener(emits, key)
404411
) {
405412
return true
@@ -441,7 +448,7 @@ function hasPropsChanged(
441448
for (let i = 0; i < nextKeys.length; i++) {
442449
const key = nextKeys[i]
443450
if (
444-
nextProps[key] !== prevProps[key] &&
451+
hasPropValueChanged(nextProps, prevProps, key) &&
445452
!isEmitListener(emitsOptions, key)
446453
) {
447454
return true
@@ -450,6 +457,19 @@ function hasPropsChanged(
450457
return false
451458
}
452459

460+
function hasPropValueChanged(
461+
nextProps: Data,
462+
prevProps: Data,
463+
key: string,
464+
): boolean {
465+
const nextProp = nextProps[key]
466+
const prevProp = prevProps[key]
467+
if (key === 'style' && isObject(nextProp) && isObject(prevProp)) {
468+
return !looseEqual(nextProp, prevProp)
469+
}
470+
return nextProp !== prevProp
471+
}
472+
453473
export function updateHOCHostEl(
454474
{ vnode, parent }: ComponentInternalInstance,
455475
el: typeof vnode.el, // HostNode

0 commit comments

Comments
 (0)