fix(runtime-core): cache props default values to avoid unnecessary watcher trigger (#3474)

fix #3471
This commit is contained in:
HcySunYang 2021-03-26 05:26:58 +08:00 committed by GitHub
parent ebedcccdc0
commit 44166b43d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 61 additions and 5 deletions

View File

@ -10,7 +10,8 @@ import {
serializeInner, serializeInner,
createApp, createApp,
provide, provide,
inject inject,
watch
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { render as domRender, nextTick } from 'vue' import { render as domRender, nextTick } from 'vue'
@ -420,4 +421,43 @@ describe('component props', () => {
expect(serializeInner(root)).toMatch('<div>60000000100000111</div>') expect(serializeInner(root)).toMatch('<div>60000000100000111</div>')
}) })
// #3474
test('should cache the value returned from the default factory to avoid unnecessary watcher trigger', async () => {
let count = 0
const Comp = {
props: {
foo: {
type: Object,
default: () => ({ val: 1 })
},
bar: Number
},
setup(props: any) {
watch(
() => props.foo,
() => {
count++
}
)
return () => h('h1', [props.foo.val, props.bar])
}
}
const foo = ref()
const bar = ref(0)
const app = createApp({
render: () => h(Comp, { foo: foo.value, bar: bar.value })
})
const root = nodeOps.createElement('div')
app.mount(root)
expect(serializeInner(root)).toMatch(`<h1>10</h1>`)
expect(count).toBe(0)
bar.value++
await nextTick()
expect(serializeInner(root)).toMatch(`<h1>11</h1>`)
expect(count).toBe(0)
})
}) })

View File

@ -302,7 +302,12 @@ export interface ComponentInternalInstance {
* @internal * @internal
*/ */
emitted: Record<string, boolean> | null emitted: Record<string, boolean> | null
/**
* used for caching the value returned from props default factory functions to
* avoid unnecessary watcher trigger
* @internal
*/
propsDefaults: Data
/** /**
* setup related * setup related
* @internal * @internal
@ -440,6 +445,9 @@ export function createComponentInstance(
emit: null as any, // to be set immediately emit: null as any, // to be set immediately
emitted: null, emitted: null,
// props default value
propsDefaults: EMPTY_OBJ,
// state // state
ctx: EMPTY_OBJ, ctx: EMPTY_OBJ,
data: EMPTY_OBJ, data: EMPTY_OBJ,

View File

@ -139,6 +139,9 @@ export function initProps(
const props: Data = {} const props: Data = {}
const attrs: Data = {} const attrs: Data = {}
def(attrs, InternalObjectKey, 1) def(attrs, InternalObjectKey, 1)
instance.propsDefaults = Object.create(null)
setFullProps(instance, rawProps, props, attrs) setFullProps(instance, rawProps, props, attrs)
// validation // validation
if (__DEV__) { if (__DEV__) {
@ -326,9 +329,14 @@ function resolvePropValue(
if (hasDefault && value === undefined) { if (hasDefault && value === undefined) {
const defaultValue = opt.default const defaultValue = opt.default
if (opt.type !== Function && isFunction(defaultValue)) { if (opt.type !== Function && isFunction(defaultValue)) {
setCurrentInstance(instance) const { propsDefaults } = instance
value = defaultValue(props) if (key in propsDefaults) {
setCurrentInstance(null) value = propsDefaults[key]
} else {
setCurrentInstance(instance)
value = propsDefaults[key] = defaultValue(props)
setCurrentInstance(null)
}
} else { } else {
value = defaultValue value = defaultValue
} }