]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): should not cast prop value if prop did not change
authorEvan You <yyx990803@gmail.com>
Mon, 20 Apr 2020 18:16:25 +0000 (14:16 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 20 Apr 2020 18:16:25 +0000 (14:16 -0400)
fix #999

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

index 9e8299ffcc3051b93c2a6bb74094f7aa48bb1853..26549cb1eff138f73cc4ec09921b8c22ec8f770a 100644 (file)
@@ -156,13 +156,15 @@ describe('component props', () => {
 
   test('default value', () => {
     let proxy: any
+    const defaultFn = jest.fn(() => ({ a: 1 }))
+
     const Comp = {
       props: {
         foo: {
           default: 1
         },
         bar: {
-          default: () => ({ a: 1 })
+          default: defaultFn
         }
       },
       render() {
@@ -173,19 +175,32 @@ describe('component props', () => {
     const root = nodeOps.createElement('div')
     render(h(Comp, { foo: 2 }), root)
     expect(proxy.foo).toBe(2)
+    const prevBar = proxy.bar
+    expect(proxy.bar).toEqual({ a: 1 })
+    expect(defaultFn).toHaveBeenCalledTimes(1)
+
+    // #999: updates should not cause default factory of unchanged prop to be
+    // called again
+    render(h(Comp, { foo: 3 }), root)
+    expect(proxy.foo).toBe(3)
     expect(proxy.bar).toEqual({ a: 1 })
+    expect(proxy.bar).toBe(prevBar)
+    expect(defaultFn).toHaveBeenCalledTimes(1)
 
     render(h(Comp, { bar: { b: 2 } }), root)
     expect(proxy.foo).toBe(1)
     expect(proxy.bar).toEqual({ b: 2 })
+    expect(defaultFn).toHaveBeenCalledTimes(1)
 
     render(h(Comp, { foo: 3, bar: { b: 3 } }), root)
     expect(proxy.foo).toBe(3)
     expect(proxy.bar).toEqual({ b: 3 })
+    expect(defaultFn).toHaveBeenCalledTimes(1)
 
     render(h(Comp, { bar: { b: 4 } }), root)
     expect(proxy.foo).toBe(1)
     expect(proxy.bar).toEqual({ b: 4 })
+    expect(defaultFn).toHaveBeenCalledTimes(1)
   })
 
   test('optimized props updates', async () => {
index b41480566482ae0ea7a780611a109da78e2bb030..bf40b50ce57d997fac51a2e33fff913796a6352f 100644 (file)
@@ -130,6 +130,7 @@ export function initProps(
 export function updateProps(
   instance: ComponentInternalInstance,
   rawProps: Data | null,
+  rawPrevProps: Data | null,
   optimized: boolean
 ) {
   const {
@@ -184,20 +185,26 @@ export function updateProps(
           ((kebabKey = hyphenate(key)) === key || !hasOwn(rawProps, kebabKey)))
       ) {
         if (options) {
-          props[key] = resolvePropValue(
-            options,
-            rawProps || EMPTY_OBJ,
-            key,
-            undefined
-          )
+          if (rawPrevProps && rawPrevProps[kebabKey!] !== undefined) {
+            props[key] = resolvePropValue(
+              options,
+              rawProps || EMPTY_OBJ,
+              key,
+              undefined
+            )
+          }
         } else {
           delete props[key]
         }
       }
     }
-    for (const key in attrs) {
-      if (!rawProps || !hasOwn(rawProps, key)) {
-        delete attrs[key]
+    // in the case of functional component w/o props declaration, props and
+    // attrs point to the same object so it should already have been updated.
+    if (attrs !== rawCurrentProps) {
+      for (const key in attrs) {
+        if (!rawProps || !hasOwn(rawProps, key)) {
+          delete attrs[key]
+        }
       }
     }
   }
@@ -240,9 +247,15 @@ function setFullProps(
   }
 
   if (needCastKeys) {
+    const rawCurrentProps = toRaw(props)
     for (let i = 0; i < needCastKeys.length; i++) {
       const key = needCastKeys[i]
-      props[key] = resolvePropValue(options!, props, key, props[key])
+      props[key] = resolvePropValue(
+        options!,
+        rawCurrentProps,
+        key,
+        rawCurrentProps[key]
+      )
     }
   }
 }
index 0df8576ef26490ba65a77c49b55971d310a5cd55..c31667458f9454061779c016eae5cdb2cf67229e 100644 (file)
@@ -1246,9 +1246,10 @@ function baseCreateRenderer(
     optimized: boolean
   ) => {
     nextVNode.component = instance
+    const prevProps = instance.vnode.props
     instance.vnode = nextVNode
     instance.next = null
-    updateProps(instance, nextVNode.props, optimized)
+    updateProps(instance, nextVNode.props, prevProps, optimized)
     updateSlots(instance, nextVNode.children)
   }