feat(compiler-core/v-model): avoid patching v-model handler when possible
This commit is contained in:
parent
48b79d02e8
commit
5481f76ce8
@ -8,7 +8,7 @@ export default function render() {
|
||||
return (openBlock(), createBlock(\\"input\\", {
|
||||
modelValue: _ctx.model[_ctx.index],
|
||||
\\"onUpdate:modelValue\\": $event => (_ctx.model[_ctx.index] = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\"]))
|
||||
}"
|
||||
`;
|
||||
|
||||
@ -35,7 +35,7 @@ export default function render() {
|
||||
return (openBlock(), createBlock(\\"input\\", {
|
||||
modelValue: _ctx.model,
|
||||
\\"onUpdate:modelValue\\": $event => (_ctx.model = $event)
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
|
||||
}, null, 8 /* PROPS */, [\\"modelValue\\"]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
@ -5,18 +5,29 @@ import {
|
||||
ElementNode,
|
||||
ObjectExpression,
|
||||
CompilerOptions,
|
||||
CallExpression
|
||||
CallExpression,
|
||||
ForNode,
|
||||
PlainElementNode,
|
||||
PlainElementCodegenNode,
|
||||
ComponentNode
|
||||
} from '../../src'
|
||||
import { ErrorCodes } from '../../src/errors'
|
||||
import { transformModel } from '../../src/transforms/vModel'
|
||||
import { transformElement } from '../../src/transforms/transformElement'
|
||||
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||
import { transformFor } from '../../src/transforms/vFor'
|
||||
import { trackSlotScopes } from '../../src/transforms/vSlot'
|
||||
|
||||
function parseWithVModel(template: string, options: CompilerOptions = {}) {
|
||||
const ast = parse(template)
|
||||
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformExpression, transformElement],
|
||||
nodeTransforms: [
|
||||
transformFor,
|
||||
transformExpression,
|
||||
trackSlotScopes,
|
||||
transformElement
|
||||
],
|
||||
directiveTransforms: {
|
||||
...options.directiveTransforms,
|
||||
model: transformModel
|
||||
@ -327,6 +338,39 @@ describe('compiler: transform v-model', () => {
|
||||
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should not mark update handler dynamic', () => {
|
||||
const root = parseWithVModel('<input v-model="foo" />', {
|
||||
prefixIdentifiers: true
|
||||
})
|
||||
const codegen = (root.children[0] as PlainElementNode)
|
||||
.codegenNode as PlainElementCodegenNode
|
||||
expect(codegen.arguments[4]).toBe(`["modelValue"]`)
|
||||
})
|
||||
|
||||
test('should mark update handler dynamic if it refers v-for scope variables', () => {
|
||||
const root = parseWithVModel(
|
||||
'<input v-for="i in list" v-model="foo[i]" />',
|
||||
{
|
||||
prefixIdentifiers: true
|
||||
}
|
||||
)
|
||||
const codegen = ((root.children[0] as ForNode)
|
||||
.children[0] as PlainElementNode).codegenNode as PlainElementCodegenNode
|
||||
expect(codegen.arguments[4]).toBe(`["modelValue", "onUpdate:modelValue"]`)
|
||||
})
|
||||
|
||||
test('should mark update handler dynamic if it refers slot scope variables', () => {
|
||||
const root = parseWithVModel(
|
||||
'<Comp v-slot="{ foo }"><input v-model="foo"/></Comp>',
|
||||
{
|
||||
prefixIdentifiers: true
|
||||
}
|
||||
)
|
||||
const codegen = ((root.children[0] as ComponentNode)
|
||||
.children[0] as PlainElementNode).codegenNode as PlainElementCodegenNode
|
||||
expect(codegen.arguments[4]).toBe(`["modelValue", "onUpdate:modelValue"]`)
|
||||
})
|
||||
|
||||
describe('errors', () => {
|
||||
test('missing expression', () => {
|
||||
const onError = jest.fn()
|
||||
|
@ -193,21 +193,21 @@ export function buildProps(
|
||||
const analyzePatchFlag = ({ key, value }: Property) => {
|
||||
if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
|
||||
if (
|
||||
value.type !== NodeTypes.SIMPLE_EXPRESSION ||
|
||||
// E.g: <p :foo="1 + 2" />.
|
||||
// Do not add prop `foo` to `dynamicPropNames`.
|
||||
(!value.isStatic && !value.isConstant)
|
||||
(value.type === NodeTypes.SIMPLE_EXPRESSION ||
|
||||
value.type === NodeTypes.COMPOUND_EXPRESSION) &&
|
||||
isStaticNode(value)
|
||||
) {
|
||||
const name = key.content
|
||||
if (name === 'ref') {
|
||||
hasRef = true
|
||||
} else if (name === 'class') {
|
||||
hasClassBinding = true
|
||||
} else if (name === 'style') {
|
||||
hasStyleBinding = true
|
||||
} else if (name !== 'key') {
|
||||
dynamicPropNames.push(name)
|
||||
}
|
||||
return
|
||||
}
|
||||
const name = key.content
|
||||
if (name === 'ref') {
|
||||
hasRef = true
|
||||
} else if (name === 'class') {
|
||||
hasClassBinding = true
|
||||
} else if (name === 'style') {
|
||||
hasStyleBinding = true
|
||||
} else if (name !== 'key') {
|
||||
dynamicPropNames.push(name)
|
||||
}
|
||||
} else {
|
||||
hasDynamicKeys = true
|
||||
|
@ -1,13 +1,16 @@
|
||||
import { DirectiveTransform } from '../transform'
|
||||
import { DirectiveTransform, TransformContext } from '../transform'
|
||||
import {
|
||||
createSimpleExpression,
|
||||
createObjectProperty,
|
||||
createCompoundExpression,
|
||||
NodeTypes,
|
||||
Property
|
||||
Property,
|
||||
CompoundExpressionNode,
|
||||
createInterpolation
|
||||
} from '../ast'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import { isMemberExpression } from '../utils'
|
||||
import { isObject } from '@vue/shared'
|
||||
|
||||
export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||
const { exp, arg } = dir
|
||||
@ -38,13 +41,23 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||
])
|
||||
: createSimpleExpression('onUpdate:modelValue', true)
|
||||
|
||||
let assignmentChildren =
|
||||
exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children
|
||||
// For a member expression used in assignment, it only needs to be updated
|
||||
// if the expression involves scope variables. Otherwise we can mark the
|
||||
// expression as constant to avoid it being included in `dynamicPropNames`
|
||||
// of the element. This optimization relies on `prefixIdentifiers: true`.
|
||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||
assignmentChildren = assignmentChildren.map(c => toConstant(c, context))
|
||||
}
|
||||
|
||||
const props = [
|
||||
createObjectProperty(propName, dir.exp!),
|
||||
createObjectProperty(
|
||||
eventName,
|
||||
createCompoundExpression([
|
||||
`$event => (`,
|
||||
...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
|
||||
...assignmentChildren,
|
||||
` = $event)`
|
||||
])
|
||||
)
|
||||
@ -57,6 +70,30 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||
return createTransformProps(props)
|
||||
}
|
||||
|
||||
function toConstant(
|
||||
exp: CompoundExpressionNode | CompoundExpressionNode['children'][0],
|
||||
context: TransformContext
|
||||
): any {
|
||||
if (!isObject(exp) || exp.type === NodeTypes.TEXT) {
|
||||
return exp
|
||||
}
|
||||
if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||
if (exp.isStatic || context.identifiers[exp.content]) {
|
||||
return exp
|
||||
}
|
||||
return {
|
||||
...exp,
|
||||
isConstant: true
|
||||
}
|
||||
} else if (exp.type === NodeTypes.COMPOUND_EXPRESSION) {
|
||||
return createCompoundExpression(
|
||||
exp.children.map(c => toConstant(c, context))
|
||||
)
|
||||
} else if (exp.type === NodeTypes.INTERPOLATION) {
|
||||
return createInterpolation(toConstant(exp.content, context), exp.loc)
|
||||
}
|
||||
}
|
||||
|
||||
function createTransformProps(props: Property[] = []) {
|
||||
return { props, needRuntime: false }
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user