diff --git a/packages/compiler-core/__tests__/transforms/vOn.spec.ts b/packages/compiler-core/__tests__/transforms/vOn.spec.ts
index 996d63e4..191f4cb5 100644
--- a/packages/compiler-core/__tests__/transforms/vOn.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vOn.spec.ts
@@ -13,7 +13,7 @@ import { transformElement } from '../../src/transforms/transformElement'
import { transformExpression } from '../../src/transforms/transformExpression'
function parseWithVOn(template: string, options: CompilerOptions = {}) {
- const ast = parse(template)
+ const ast = parse(template, options)
transform(ast, {
nodeTransforms: [transformExpression, transformElement],
directiveTransforms: {
@@ -405,6 +405,15 @@ describe('compiler: transform v-on', () => {
})
})
+ test('bail on component member expression handler', () => {
+ const { root } = parseWithVOn(``, {
+ prefixIdentifiers: true,
+ cacheHandlers: true,
+ isNativeTag: tag => tag === 'div'
+ })
+ expect(root.cached).toBe(0)
+ })
+
test('inline function expression handler', () => {
const { root, node } = parseWithVOn(`
`, {
prefixIdentifiers: true,
diff --git a/packages/compiler-core/src/transforms/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts
index 40e61a3f..cffe9b66 100644
--- a/packages/compiler-core/src/transforms/vOn.ts
+++ b/packages/compiler-core/src/transforms/vOn.ts
@@ -6,7 +6,8 @@ import {
ExpressionNode,
NodeTypes,
createCompoundExpression,
- SimpleExpressionNode
+ SimpleExpressionNode,
+ ElementTypes
} from '../ast'
import { capitalize, camelize } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
@@ -76,7 +77,16 @@ export const transformOn: DirectiveTransform = (
// with scope analysis, the function is hoistable if it has no reference
// to scope variables.
isCacheable =
- context.cacheHandlers && !hasScopeRef(exp, context.identifiers)
+ context.cacheHandlers &&
+ // #1541 bail if this is a member exp handler passed to a component -
+ // we need to use the original function to preserve arity,
+ // e.g. relies on checking cb.length to determine
+ // transition end handling. Inline function is ok since its arity
+ // is preserved even when cached.
+ !(isMemberExp && node.tagType === ElementTypes.COMPONENT) &&
+ // bail if the function references closure variables (v-for, v-slot)
+ // it must be passed fresh to avoid stale values.
+ !hasScopeRef(exp, context.identifiers)
// If the expression is optimizable and is a member expression pointing
// to a function, turn it into invocation (and wrap in an arrow function
// below) so that it always accesses the latest value when called - thus