Browse Source

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

fix #3471
HcySunYang 5 năm trước cách đây
mục cha
commit
44166b43d9

+ 41 - 1
packages/runtime-core/__tests__/componentProps.spec.ts

@@ -10,7 +10,8 @@ import {
   serializeInner,
   createApp,
   provide,
-  inject
+  inject,
+  watch
 } from '@vue/runtime-test'
 import { render as domRender, nextTick } from 'vue'
 
@@ -420,4 +421,43 @@ describe('component props', () => {
 
     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)
+  })
 })

+ 9 - 1
packages/runtime-core/src/component.ts

@@ -302,7 +302,12 @@ export interface ComponentInternalInstance {
    * @internal
    */
   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
    * @internal
@@ -440,6 +445,9 @@ export function createComponentInstance(
     emit: null as any, // to be set immediately
     emitted: null,
 
+    // props default value
+    propsDefaults: EMPTY_OBJ,
+
     // state
     ctx: EMPTY_OBJ,
     data: EMPTY_OBJ,

+ 11 - 3
packages/runtime-core/src/componentProps.ts

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