]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): avoid double-setting props when casting
authorEvan You <yyx990803@gmail.com>
Mon, 24 May 2021 22:48:33 +0000 (18:48 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 24 May 2021 22:48:33 +0000 (18:48 -0400)
fix #3371, close #3384

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

index 658ef5c567acc636883b5ad60011d38cd7c29d3e..ac1dc13dfcca6fb718dc7b03bbd0df45cbffef26 100644 (file)
@@ -12,7 +12,8 @@ import {
   provide,
   inject,
   watch,
-  toRefs
+  toRefs,
+  SetupContext
 } from '@vue/runtime-test'
 import { render as domRender, nextTick } from 'vue'
 
@@ -508,4 +509,51 @@ describe('component props', () => {
     await nextTick()
     expect(changeSpy).toHaveBeenCalledTimes(1)
   })
+
+  // #3371
+  test(`avoid double-setting props when casting`, async () => {
+    const Parent = {
+      setup(props: any, { slots }: SetupContext) {
+        const childProps = ref()
+        const registerChildProps = (props: any) => {
+          childProps.value = props
+        }
+        provide('register', registerChildProps)
+
+        return () => {
+          // access the child component's props
+          childProps.value && childProps.value.foo
+          return slots.default!()
+        }
+      }
+    }
+
+    const Child = {
+      props: {
+        foo: {
+          type: Boolean,
+          required: false
+        }
+      },
+      setup(props: { foo: boolean }) {
+        const register = inject('register') as any
+        // 1. change the reactivity data of the parent component
+        // 2. register its own props to the parent component
+        register(props)
+
+        return () => 'foo'
+      }
+    }
+
+    const App = {
+      setup() {
+        return () => h(Parent, () => h(Child as any, { foo: '' }, () => null))
+      }
+    }
+
+    const root = nodeOps.createElement('div')
+    render(h(App), root)
+    await nextTick()
+    expect(serializeInner(root)).toBe(`foo`)
+  })
 })
index d86eb9698947da8f21187ab4b907e19e4f07ecf5..f94d1522ccd649b0656a098a25a54d2e6f217533 100644 (file)
@@ -312,6 +312,7 @@ function setFullProps(
 ) {
   const [options, needCastKeys] = instance.propsOptions
   let hasAttrsChanged = false
+  let rawCastValues: Data | undefined
   if (rawProps) {
     for (let key in rawProps) {
       // key, ref are reserved and never passed down
@@ -337,7 +338,11 @@ function setFullProps(
       // kebab -> camel conversion here we need to camelize the key.
       let camelKey
       if (options && hasOwn(options, (camelKey = camelize(key)))) {
-        props[camelKey] = value
+        if (!needCastKeys || !needCastKeys.includes(camelKey)) {
+          props[camelKey] = value
+        } else {
+          ;(rawCastValues || (rawCastValues = {}))[camelKey] = value
+        }
       } else if (!isEmitListener(instance.emitsOptions, key)) {
         // Any non-declared (either as a prop or an emitted event) props are put
         // into a separate `attrs` object for spreading. Make sure to preserve
@@ -358,14 +363,13 @@ function setFullProps(
   }
 
   if (needCastKeys) {
-    const rawCurrentProps = toRaw(props)
     for (let i = 0; i < needCastKeys.length; i++) {
       const key = needCastKeys[i]
       props[key] = resolvePropValue(
         options!,
-        rawCurrentProps,
+        rawCastValues || EMPTY_OBJ,
         key,
-        rawCurrentProps[key],
+        rawCastValues && rawCastValues[key],
         instance
       )
     }
@@ -408,13 +412,13 @@ function resolvePropValue(
     }
     // boolean casting
     if (opt[BooleanFlags.shouldCast]) {
-      if (!hasOwn(props, key) && !hasDefault) {
-        value = false
-      } else if (
+      if (
         opt[BooleanFlags.shouldCastTrue] &&
         (value === '' || value === hyphenate(key))
       ) {
         value = true
+      } else if (!hasOwn(props, key) && !hasDefault) {
+        value = false
       }
     }
   }