From 1f7dcb234f5ba04ce145bf02a21ec3fb0dafc99d Mon Sep 17 00:00:00 2001 From: HcySunYang Date: Sun, 7 Mar 2021 19:56:00 +0800 Subject: [PATCH] fix(runtime-core): avoid infinite loop rendering caused by setting props multiple times --- .../__tests__/rendererComponent.spec.ts | 47 +++++++++++++++++++ packages/runtime-core/src/componentProps.ts | 11 +++-- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/__tests__/rendererComponent.spec.ts b/packages/runtime-core/__tests__/rendererComponent.spec.ts index 02070d384e9..85ead165402 100644 --- a/packages/runtime-core/__tests__/rendererComponent.spec.ts +++ b/packages/runtime-core/__tests__/rendererComponent.spec.ts @@ -296,4 +296,51 @@ describe('renderer: component', () => { expect(serializeInner(root)).toBe(`

1

`) }) }) + + // #3371 + test(`should not cause an infinite loop when the child component's props track the parent component's render fn`, 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`) + }) }) diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index a72356184a0..53faaf5851a 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -312,6 +312,7 @@ function setFullProps( ) { const [options, needCastKeys] = instance.propsOptions let hasAttrsChanged = false + const rawCurrentProps = extend({}, toRaw(props)) if (rawProps) { for (let key in rawProps) { // key, ref are reserved and never passed down @@ -337,7 +338,7 @@ function setFullProps( // kebab -> camel conversion here we need to camelize the key. let camelKey if (options && hasOwn(options, (camelKey = camelize(key)))) { - props[camelKey] = value + rawCurrentProps[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,7 +359,6 @@ function setFullProps( } if (needCastKeys) { - const rawCurrentProps = toRaw(props) for (let i = 0; i < needCastKeys.length; i++) { const key = needCastKeys[i] props[key] = resolvePropValue( @@ -370,7 +370,12 @@ function setFullProps( ) } } - + if (options) { + for (const key in rawCurrentProps) { + if (!needCastKeys || !needCastKeys.includes(key)) + props[key] = rawCurrentProps[key] + } + } return hasAttrsChanged }