fix(runtime-core): avoid double-setting props when casting

fix #3371, close #3384
This commit is contained in:
Evan You 2021-05-24 18:48:33 -04:00
parent 4ce0df6ef1
commit 0255be2f4b
2 changed files with 60 additions and 8 deletions

View File

@ -12,7 +12,8 @@ import {
provide, provide,
inject, inject,
watch, watch,
toRefs toRefs,
SetupContext
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { render as domRender, nextTick } from 'vue' import { render as domRender, nextTick } from 'vue'
@ -508,4 +509,51 @@ describe('component props', () => {
await nextTick() await nextTick()
expect(changeSpy).toHaveBeenCalledTimes(1) 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`)
})
}) })

View File

@ -312,6 +312,7 @@ function setFullProps(
) { ) {
const [options, needCastKeys] = instance.propsOptions const [options, needCastKeys] = instance.propsOptions
let hasAttrsChanged = false let hasAttrsChanged = false
let rawCastValues: Data | undefined
if (rawProps) { if (rawProps) {
for (let key in rawProps) { for (let key in rawProps) {
// key, ref are reserved and never passed down // key, ref are reserved and never passed down
@ -337,7 +338,11 @@ function setFullProps(
// kebab -> camel conversion here we need to camelize the key. // kebab -> camel conversion here we need to camelize the key.
let camelKey let camelKey
if (options && hasOwn(options, (camelKey = camelize(key)))) { 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)) { } else if (!isEmitListener(instance.emitsOptions, key)) {
// Any non-declared (either as a prop or an emitted event) props are put // 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 // into a separate `attrs` object for spreading. Make sure to preserve
@ -358,14 +363,13 @@ function setFullProps(
} }
if (needCastKeys) { if (needCastKeys) {
const rawCurrentProps = toRaw(props)
for (let i = 0; i < needCastKeys.length; i++) { for (let i = 0; i < needCastKeys.length; i++) {
const key = needCastKeys[i] const key = needCastKeys[i]
props[key] = resolvePropValue( props[key] = resolvePropValue(
options!, options!,
rawCurrentProps, rawCastValues || EMPTY_OBJ,
key, key,
rawCurrentProps[key], rawCastValues && rawCastValues[key],
instance instance
) )
} }
@ -408,13 +412,13 @@ function resolvePropValue(
} }
// boolean casting // boolean casting
if (opt[BooleanFlags.shouldCast]) { if (opt[BooleanFlags.shouldCast]) {
if (!hasOwn(props, key) && !hasDefault) { if (
value = false
} else if (
opt[BooleanFlags.shouldCastTrue] && opt[BooleanFlags.shouldCastTrue] &&
(value === '' || value === hyphenate(key)) (value === '' || value === hyphenate(key))
) { ) {
value = true value = true
} else if (!hasOwn(props, key) && !hasDefault) {
value = false
} }
} }
} }