fix(runtime-core): avoid double-setting props when casting
fix #3371, close #3384
This commit is contained in:
parent
4ce0df6ef1
commit
0255be2f4b
@ -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`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user