Skip to content

Commit 4fe4de0

Browse files
committed
fix(runtime-core): ensure declare prop keys are always present
fix #3288
1 parent f0cf14b commit 4fe4de0

File tree

2 files changed

+39
-2
lines changed

2 files changed

+39
-2
lines changed

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

+30-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
createApp,
1212
provide,
1313
inject,
14-
watch
14+
watch,
15+
toRefs
1516
} from '@vue/runtime-test'
1617
import { render as domRender, nextTick } from 'vue'
1718

@@ -479,4 +480,32 @@ describe('component props', () => {
479480
expect(serializeInner(root)).toMatch(`<h1>11</h1>`)
480481
expect(count).toBe(0)
481482
})
483+
484+
// #3288
485+
test('declared prop key should be present even if not passed', async () => {
486+
let initialKeys: string[] = []
487+
const changeSpy = jest.fn()
488+
const passFoo = ref(false)
489+
490+
const Comp = {
491+
render() {},
492+
props: {
493+
foo: String
494+
},
495+
setup(props: any) {
496+
initialKeys = Object.keys(props)
497+
const { foo } = toRefs(props)
498+
watch(foo, changeSpy)
499+
}
500+
}
501+
502+
const Parent = () => (passFoo.value ? h(Comp, { foo: 'ok' }) : h(Comp))
503+
const root = nodeOps.createElement('div')
504+
createApp(Parent).mount(root)
505+
506+
expect(initialKeys).toMatchObject(['foo'])
507+
passFoo.value = true
508+
await nextTick()
509+
expect(changeSpy).toHaveBeenCalledTimes(1)
510+
})
482511
})

packages/runtime-core/src/componentProps.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,14 @@ export function initProps(
143143
instance.propsDefaults = Object.create(null)
144144

145145
setFullProps(instance, rawProps, props, attrs)
146+
147+
// ensure all declared prop keys are present
148+
for (const key in instance.propsOptions[0]) {
149+
if (!(key in props)) {
150+
props[key] = undefined
151+
}
152+
}
153+
146154
// validation
147155
if (__DEV__) {
148156
validateProps(rawProps || {}, props, instance)
@@ -281,11 +289,11 @@ function setFullProps(
281289
const [options, needCastKeys] = instance.propsOptions
282290
if (rawProps) {
283291
for (const key in rawProps) {
284-
const value = rawProps[key]
285292
// key, ref are reserved and never passed down
286293
if (isReservedProp(key)) {
287294
continue
288295
}
296+
const value = rawProps[key]
289297
// prop option names are camelized during normalization, so to support
290298
// kebab -> camel conversion here we need to camelize the key.
291299
let camelKey

0 commit comments

Comments
 (0)