From 171cfa404f33a451376dcb84d66ddae012c343ec Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 20 Apr 2020 14:16:25 -0400 Subject: [PATCH] fix(runtime-core): should not cast prop value if prop did not change fix #999 --- .../__tests__/componentProps.spec.ts | 17 +++++++++- packages/runtime-core/src/componentProps.ts | 33 +++++++++++++------ packages/runtime-core/src/renderer.ts | 3 +- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/packages/runtime-core/__tests__/componentProps.spec.ts b/packages/runtime-core/__tests__/componentProps.spec.ts index 9e8299ffc..26549cb1e 100644 --- a/packages/runtime-core/__tests__/componentProps.spec.ts +++ b/packages/runtime-core/__tests__/componentProps.spec.ts @@ -156,13 +156,15 @@ describe('component props', () => { test('default value', () => { let proxy: any + const defaultFn = jest.fn(() => ({ a: 1 })) + const Comp = { props: { foo: { default: 1 }, bar: { - default: () => ({ a: 1 }) + default: defaultFn } }, render() { @@ -173,19 +175,32 @@ describe('component props', () => { const root = nodeOps.createElement('div') render(h(Comp, { foo: 2 }), root) expect(proxy.foo).toBe(2) + const prevBar = proxy.bar expect(proxy.bar).toEqual({ a: 1 }) + expect(defaultFn).toHaveBeenCalledTimes(1) + + // #999: updates should not cause default factory of unchanged prop to be + // called again + render(h(Comp, { foo: 3 }), root) + expect(proxy.foo).toBe(3) + expect(proxy.bar).toEqual({ a: 1 }) + expect(proxy.bar).toBe(prevBar) + expect(defaultFn).toHaveBeenCalledTimes(1) render(h(Comp, { bar: { b: 2 } }), root) expect(proxy.foo).toBe(1) expect(proxy.bar).toEqual({ b: 2 }) + expect(defaultFn).toHaveBeenCalledTimes(1) render(h(Comp, { foo: 3, bar: { b: 3 } }), root) expect(proxy.foo).toBe(3) expect(proxy.bar).toEqual({ b: 3 }) + expect(defaultFn).toHaveBeenCalledTimes(1) render(h(Comp, { bar: { b: 4 } }), root) expect(proxy.foo).toBe(1) expect(proxy.bar).toEqual({ b: 4 }) + expect(defaultFn).toHaveBeenCalledTimes(1) }) test('optimized props updates', async () => { diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index b41480566..bf40b50ce 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -130,6 +130,7 @@ export function initProps( export function updateProps( instance: ComponentInternalInstance, rawProps: Data | null, + rawPrevProps: Data | null, optimized: boolean ) { const { @@ -184,20 +185,26 @@ export function updateProps( ((kebabKey = hyphenate(key)) === key || !hasOwn(rawProps, kebabKey))) ) { if (options) { - props[key] = resolvePropValue( - options, - rawProps || EMPTY_OBJ, - key, - undefined - ) + if (rawPrevProps && rawPrevProps[kebabKey!] !== undefined) { + props[key] = resolvePropValue( + options, + rawProps || EMPTY_OBJ, + key, + undefined + ) + } } else { delete props[key] } } } - for (const key in attrs) { - if (!rawProps || !hasOwn(rawProps, key)) { - delete attrs[key] + // in the case of functional component w/o props declaration, props and + // attrs point to the same object so it should already have been updated. + if (attrs !== rawCurrentProps) { + for (const key in attrs) { + if (!rawProps || !hasOwn(rawProps, key)) { + delete attrs[key] + } } } } @@ -240,9 +247,15 @@ function setFullProps( } if (needCastKeys) { + const rawCurrentProps = toRaw(props) for (let i = 0; i < needCastKeys.length; i++) { const key = needCastKeys[i] - props[key] = resolvePropValue(options!, props, key, props[key]) + props[key] = resolvePropValue( + options!, + rawCurrentProps, + key, + rawCurrentProps[key] + ) } } } diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 0df8576ef..c31667458 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -1246,9 +1246,10 @@ function baseCreateRenderer( optimized: boolean ) => { nextVNode.component = instance + const prevProps = instance.vnode.props instance.vnode = nextVNode instance.next = null - updateProps(instance, nextVNode.props, optimized) + updateProps(instance, nextVNode.props, prevProps, optimized) updateSlots(instance, nextVNode.children) }