From 4fe4de0a49ffc2461b0394e74674af38ff5e2a20 Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 1 Apr 2021 20:25:12 -0400 Subject: [PATCH] fix(runtime-core): ensure declare prop keys are always present fix #3288 --- .../__tests__/componentProps.spec.ts | 31 ++++++++++++++++++- packages/runtime-core/src/componentProps.ts | 10 +++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/__tests__/componentProps.spec.ts b/packages/runtime-core/__tests__/componentProps.spec.ts index a97c9d48190..658ef5c567a 100644 --- a/packages/runtime-core/__tests__/componentProps.spec.ts +++ b/packages/runtime-core/__tests__/componentProps.spec.ts @@ -11,7 +11,8 @@ import { createApp, provide, inject, - watch + watch, + toRefs } from '@vue/runtime-test' import { render as domRender, nextTick } from 'vue' @@ -479,4 +480,32 @@ describe('component props', () => { expect(serializeInner(root)).toMatch(`

11

`) expect(count).toBe(0) }) + + // #3288 + test('declared prop key should be present even if not passed', async () => { + let initialKeys: string[] = [] + const changeSpy = jest.fn() + const passFoo = ref(false) + + const Comp = { + render() {}, + props: { + foo: String + }, + setup(props: any) { + initialKeys = Object.keys(props) + const { foo } = toRefs(props) + watch(foo, changeSpy) + } + } + + const Parent = () => (passFoo.value ? h(Comp, { foo: 'ok' }) : h(Comp)) + const root = nodeOps.createElement('div') + createApp(Parent).mount(root) + + expect(initialKeys).toMatchObject(['foo']) + passFoo.value = true + await nextTick() + expect(changeSpy).toHaveBeenCalledTimes(1) + }) }) diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 476fba887dc..0238bf80e28 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -143,6 +143,14 @@ export function initProps( instance.propsDefaults = Object.create(null) setFullProps(instance, rawProps, props, attrs) + + // ensure all declared prop keys are present + for (const key in instance.propsOptions[0]) { + if (!(key in props)) { + props[key] = undefined + } + } + // validation if (__DEV__) { validateProps(rawProps || {}, props, instance) @@ -281,11 +289,11 @@ function setFullProps( const [options, needCastKeys] = instance.propsOptions if (rawProps) { for (const key in rawProps) { - const value = rawProps[key] // key, ref are reserved and never passed down if (isReservedProp(key)) { continue } + const value = rawProps[key] // prop option names are camelized during normalization, so to support // kebab -> camel conversion here we need to camelize the key. let camelKey