Skip to content

Commit 44166b4

Browse files
authored
fix(runtime-core): cache props default values to avoid unnecessary watcher trigger (#3474)
fix #3471
1 parent ebedccc commit 44166b4

File tree

3 files changed

+61
-5
lines changed

3 files changed

+61
-5
lines changed

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

+41-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
serializeInner,
1111
createApp,
1212
provide,
13-
inject
13+
inject,
14+
watch
1415
} from '@vue/runtime-test'
1516
import { render as domRender, nextTick } from 'vue'
1617

@@ -420,4 +421,43 @@ describe('component props', () => {
420421

421422
expect(serializeInner(root)).toMatch('<div>60000000100000111</div>')
422423
})
424+
425+
// #3474
426+
test('should cache the value returned from the default factory to avoid unnecessary watcher trigger', async () => {
427+
let count = 0
428+
const Comp = {
429+
props: {
430+
foo: {
431+
type: Object,
432+
default: () => ({ val: 1 })
433+
},
434+
bar: Number
435+
},
436+
setup(props: any) {
437+
watch(
438+
() => props.foo,
439+
() => {
440+
count++
441+
}
442+
)
443+
return () => h('h1', [props.foo.val, props.bar])
444+
}
445+
}
446+
447+
const foo = ref()
448+
const bar = ref(0)
449+
const app = createApp({
450+
render: () => h(Comp, { foo: foo.value, bar: bar.value })
451+
})
452+
453+
const root = nodeOps.createElement('div')
454+
app.mount(root)
455+
expect(serializeInner(root)).toMatch(`<h1>10</h1>`)
456+
expect(count).toBe(0)
457+
458+
bar.value++
459+
await nextTick()
460+
expect(serializeInner(root)).toMatch(`<h1>11</h1>`)
461+
expect(count).toBe(0)
462+
})
423463
})

packages/runtime-core/src/component.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,12 @@ export interface ComponentInternalInstance {
302302
* @internal
303303
*/
304304
emitted: Record<string, boolean> | null
305-
305+
/**
306+
* used for caching the value returned from props default factory functions to
307+
* avoid unnecessary watcher trigger
308+
* @internal
309+
*/
310+
propsDefaults: Data
306311
/**
307312
* setup related
308313
* @internal
@@ -440,6 +445,9 @@ export function createComponentInstance(
440445
emit: null as any, // to be set immediately
441446
emitted: null,
442447

448+
// props default value
449+
propsDefaults: EMPTY_OBJ,
450+
443451
// state
444452
ctx: EMPTY_OBJ,
445453
data: EMPTY_OBJ,

packages/runtime-core/src/componentProps.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ export function initProps(
139139
const props: Data = {}
140140
const attrs: Data = {}
141141
def(attrs, InternalObjectKey, 1)
142+
143+
instance.propsDefaults = Object.create(null)
144+
142145
setFullProps(instance, rawProps, props, attrs)
143146
// validation
144147
if (__DEV__) {
@@ -326,9 +329,14 @@ function resolvePropValue(
326329
if (hasDefault && value === undefined) {
327330
const defaultValue = opt.default
328331
if (opt.type !== Function && isFunction(defaultValue)) {
329-
setCurrentInstance(instance)
330-
value = defaultValue(props)
331-
setCurrentInstance(null)
332+
const { propsDefaults } = instance
333+
if (key in propsDefaults) {
334+
value = propsDefaults[key]
335+
} else {
336+
setCurrentInstance(instance)
337+
value = propsDefaults[key] = defaultValue(props)
338+
setCurrentInstance(null)
339+
}
332340
} else {
333341
value = defaultValue
334342
}

0 commit comments

Comments
 (0)