fix(runtime-core): should not cast prop value if prop did not change

fix #999
This commit is contained in:
Evan You 2020-04-20 14:16:25 -04:00
parent 36d77f9a9e
commit 171cfa404f
3 changed files with 41 additions and 12 deletions

View File

@ -156,13 +156,15 @@ describe('component props', () => {
test('default value', () => { test('default value', () => {
let proxy: any let proxy: any
const defaultFn = jest.fn(() => ({ a: 1 }))
const Comp = { const Comp = {
props: { props: {
foo: { foo: {
default: 1 default: 1
}, },
bar: { bar: {
default: () => ({ a: 1 }) default: defaultFn
} }
}, },
render() { render() {
@ -173,19 +175,32 @@ describe('component props', () => {
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
render(h(Comp, { foo: 2 }), root) render(h(Comp, { foo: 2 }), root)
expect(proxy.foo).toBe(2) expect(proxy.foo).toBe(2)
const prevBar = proxy.bar
expect(proxy.bar).toEqual({ a: 1 }) 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) render(h(Comp, { bar: { b: 2 } }), root)
expect(proxy.foo).toBe(1) expect(proxy.foo).toBe(1)
expect(proxy.bar).toEqual({ b: 2 }) expect(proxy.bar).toEqual({ b: 2 })
expect(defaultFn).toHaveBeenCalledTimes(1)
render(h(Comp, { foo: 3, bar: { b: 3 } }), root) render(h(Comp, { foo: 3, bar: { b: 3 } }), root)
expect(proxy.foo).toBe(3) expect(proxy.foo).toBe(3)
expect(proxy.bar).toEqual({ b: 3 }) expect(proxy.bar).toEqual({ b: 3 })
expect(defaultFn).toHaveBeenCalledTimes(1)
render(h(Comp, { bar: { b: 4 } }), root) render(h(Comp, { bar: { b: 4 } }), root)
expect(proxy.foo).toBe(1) expect(proxy.foo).toBe(1)
expect(proxy.bar).toEqual({ b: 4 }) expect(proxy.bar).toEqual({ b: 4 })
expect(defaultFn).toHaveBeenCalledTimes(1)
}) })
test('optimized props updates', async () => { test('optimized props updates', async () => {

View File

@ -130,6 +130,7 @@ export function initProps(
export function updateProps( export function updateProps(
instance: ComponentInternalInstance, instance: ComponentInternalInstance,
rawProps: Data | null, rawProps: Data | null,
rawPrevProps: Data | null,
optimized: boolean optimized: boolean
) { ) {
const { const {
@ -184,20 +185,26 @@ export function updateProps(
((kebabKey = hyphenate(key)) === key || !hasOwn(rawProps, kebabKey))) ((kebabKey = hyphenate(key)) === key || !hasOwn(rawProps, kebabKey)))
) { ) {
if (options) { if (options) {
props[key] = resolvePropValue( if (rawPrevProps && rawPrevProps[kebabKey!] !== undefined) {
options, props[key] = resolvePropValue(
rawProps || EMPTY_OBJ, options,
key, rawProps || EMPTY_OBJ,
undefined key,
) undefined
)
}
} else { } else {
delete props[key] delete props[key]
} }
} }
} }
for (const key in attrs) { // in the case of functional component w/o props declaration, props and
if (!rawProps || !hasOwn(rawProps, key)) { // attrs point to the same object so it should already have been updated.
delete attrs[key] if (attrs !== rawCurrentProps) {
for (const key in attrs) {
if (!rawProps || !hasOwn(rawProps, key)) {
delete attrs[key]
}
} }
} }
} }
@ -240,9 +247,15 @@ 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(options!, props, key, props[key]) props[key] = resolvePropValue(
options!,
rawCurrentProps,
key,
rawCurrentProps[key]
)
} }
} }
} }

View File

@ -1246,9 +1246,10 @@ function baseCreateRenderer(
optimized: boolean optimized: boolean
) => { ) => {
nextVNode.component = instance nextVNode.component = instance
const prevProps = instance.vnode.props
instance.vnode = nextVNode instance.vnode = nextVNode
instance.next = null instance.next = null
updateProps(instance, nextVNode.props, optimized) updateProps(instance, nextVNode.props, prevProps, optimized)
updateSlots(instance, nextVNode.children) updateSlots(instance, nextVNode.children)
} }