]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): cache props default values to avoid unnecessary watcher trigger...
authorHcySunYang <HcySunYang@outlook.com>
Thu, 25 Mar 2021 21:26:58 +0000 (05:26 +0800)
committerGitHub <noreply@github.com>
Thu, 25 Mar 2021 21:26:58 +0000 (17:26 -0400)
fix #3471

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

index 001e0d5da1ae6b8b79e55b416153a305e2e4415d..7c90a54a12b10ba219cc0e8f8e2464fff144b0fb 100644 (file)
@@ -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)
+  })
 })
index 79847225bd8041de4182bb6932a08c878acc103f..51147ccc8f404b16a44b6bb9396625bb9705911b 100644 (file)
@@ -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,
index 91190231fb9c6bf456cee220df085337b65536e3..39c15471f809e708f4b4108c6cc2a6e4163363b0 100644 (file)
@@ -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
       }