inject,
nextTick,
nodeOps,
+ onMounted,
provide,
ref,
render,
`Property '$attrs' was accessed via 'this'. Avoid using 'this' in templates.`,
).toHaveBeenWarned()
})
+
+ test('should not update child component without changes', async () => {
+ const text = ref(0)
+ const spy = vi.fn()
+
+ const ClientOnly = {
+ setup(_: any, { slots }: SetupContext) {
+ const mounted = ref(false)
+ onMounted(() => {
+ mounted.value = true
+ })
+ return () => {
+ if (mounted.value) {
+ return slots.default!()
+ }
+ }
+ },
+ }
+
+ const App = {
+ render() {
+ return h(ClientOnly, null, {
+ default: () => [
+ h('span', null, [text.value]),
+ h(Comp, { style: { width: '100%' } }),
+ ],
+ })
+ },
+ }
+
+ const Comp = {
+ render(this: any) {
+ spy()
+ return null
+ },
+ }
+
+ const root = nodeOps.createElement('div')
+ render(h(App), root)
+ expect(serializeInner(root)).toBe(`<!---->`)
+ await nextTick()
+
+ expect(serializeInner(root)).toBe(`<span>0</span><!---->`)
+ expect(spy).toHaveBeenCalledTimes(1)
+
+ text.value++
+ await nextTick()
+ expect(serializeInner(root)).toBe(`<span>1</span><!---->`)
+ // expect Comp to not be re-rendered
+ expect(spy).toHaveBeenCalledTimes(1)
+ })
})
normalizeVNode,
} from './vnode'
import { ErrorCodes, handleError } from './errorHandling'
-import { PatchFlags, ShapeFlags, isModelListener, isOn } from '@vue/shared'
+import {
+ PatchFlags,
+ ShapeFlags,
+ isModelListener,
+ isOn,
+ looseEqual,
+} from '@vue/shared'
import { warn } from './warning'
import { isHmrUpdating } from './hmr'
import type { NormalizedProps } from './componentProps'
for (let i = 0; i < dynamicProps.length; i++) {
const key = dynamicProps[i]
if (
- nextProps![key] !== prevProps![key] &&
+ !looseEqual(nextProps![key], prevProps![key]) &&
!isEmitListener(emits, key)
) {
return true
for (let i = 0; i < nextKeys.length; i++) {
const key = nextKeys[i]
if (
- nextProps[key] !== prevProps[key] &&
+ !looseEqual(nextProps[key], prevProps[key]) &&
!isEmitListener(emitsOptions, key)
) {
return true