perf: should not trigger child update if changed prop is declared emit listener

close #2072
This commit is contained in:
Evan You 2020-09-14 18:58:30 -04:00
parent 4de5b111ee
commit 124c385baf
2 changed files with 41 additions and 6 deletions

View File

@ -81,4 +81,27 @@ describe('renderer: component', () => {
render(h(Comp2), root) render(h(Comp2), root)
expect(serializeInner(root)).toBe('<span>foo</span>') expect(serializeInner(root)).toBe('<span>foo</span>')
}) })
// #2072
it('should not update Component if only changed props are declared emit listeners', () => {
const Comp1 = {
emits: ['foo'],
updated: jest.fn(),
render: () => null
}
const root = nodeOps.createElement('div')
render(
h(Comp1, {
onFoo: () => {}
}),
root
)
render(
h(Comp1, {
onFoo: () => {}
}),
root
)
expect(Comp1.updated).not.toHaveBeenCalled()
})
}) })

View File

@ -18,6 +18,7 @@ import { PatchFlags, ShapeFlags, isOn, isModelListener } from '@vue/shared'
import { warn } from './warning' import { warn } from './warning'
import { isHmrUpdating } from './hmr' import { isHmrUpdating } from './hmr'
import { NormalizedProps } from './componentProps' import { NormalizedProps } from './componentProps'
import { isEmitListener } from './componentEmits'
// mark the current rendering instance for asset resolution (e.g. // mark the current rendering instance for asset resolution (e.g.
// resolveComponent, resolveDirective) during render // resolveComponent, resolveDirective) during render
@ -290,8 +291,9 @@ export function shouldUpdateComponent(
nextVNode: VNode, nextVNode: VNode,
optimized?: boolean optimized?: boolean
): boolean { ): boolean {
const { props: prevProps, children: prevChildren } = prevVNode const { props: prevProps, children: prevChildren, component } = prevVNode
const { props: nextProps, children: nextChildren, patchFlag } = nextVNode const { props: nextProps, children: nextChildren, patchFlag } = nextVNode
const emits = component!.emitsOptions
// Parent component's render function was hot-updated. Since this may have // Parent component's render function was hot-updated. Since this may have
// caused the child component's slots content to have changed, we need to // caused the child component's slots content to have changed, we need to
@ -316,12 +318,15 @@ export function shouldUpdateComponent(
return !!nextProps return !!nextProps
} }
// presence of this flag indicates props are always non-null // presence of this flag indicates props are always non-null
return hasPropsChanged(prevProps, nextProps!) return hasPropsChanged(prevProps, nextProps!, emits)
} else if (patchFlag & PatchFlags.PROPS) { } else if (patchFlag & PatchFlags.PROPS) {
const dynamicProps = nextVNode.dynamicProps! const dynamicProps = nextVNode.dynamicProps!
for (let i = 0; i < dynamicProps.length; i++) { for (let i = 0; i < dynamicProps.length; i++) {
const key = dynamicProps[i] const key = dynamicProps[i]
if (nextProps![key] !== prevProps![key]) { if (
nextProps![key] !== prevProps![key] &&
!isEmitListener(emits, key)
) {
return true return true
} }
} }
@ -343,20 +348,27 @@ export function shouldUpdateComponent(
if (!nextProps) { if (!nextProps) {
return true return true
} }
return hasPropsChanged(prevProps, nextProps) return hasPropsChanged(prevProps, nextProps, emits)
} }
return false return false
} }
function hasPropsChanged(prevProps: Data, nextProps: Data): boolean { function hasPropsChanged(
prevProps: Data,
nextProps: Data,
emitsOptions: ComponentInternalInstance['emitsOptions']
): boolean {
const nextKeys = Object.keys(nextProps) const nextKeys = Object.keys(nextProps)
if (nextKeys.length !== Object.keys(prevProps).length) { if (nextKeys.length !== Object.keys(prevProps).length) {
return true return true
} }
for (let i = 0; i < nextKeys.length; i++) { for (let i = 0; i < nextKeys.length; i++) {
const key = nextKeys[i] const key = nextKeys[i]
if (nextProps[key] !== prevProps[key]) { if (
nextProps[key] !== prevProps[key] &&
!isEmitListener(emitsOptions, key)
) {
return true return true
} }
} }