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\\", {
|
return (openBlock(), createBlock(\\"input\\", {
|
||||||
modelValue: _ctx.model[_ctx.index],
|
modelValue: _ctx.model[_ctx.index],
|
||||||
\\"onUpdate:modelValue\\": $event => (_ctx.model[_ctx.index] = $event)
|
\\"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\\", {
|
return (openBlock(), createBlock(\\"input\\", {
|
||||||
modelValue: _ctx.model,
|
modelValue: _ctx.model,
|
||||||
\\"onUpdate:modelValue\\": $event => (_ctx.model = $event)
|
\\"onUpdate:modelValue\\": $event => (_ctx.model = $event)
|
||||||
}, null, 8 /* PROPS */, [\\"modelValue\\", \\"onUpdate:modelValue\\"]))
|
}, null, 8 /* PROPS */, [\\"modelValue\\"]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -5,18 +5,29 @@ import {
|
|||||||
ElementNode,
|
ElementNode,
|
||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
CompilerOptions,
|
CompilerOptions,
|
||||||
CallExpression
|
CallExpression,
|
||||||
|
ForNode,
|
||||||
|
PlainElementNode,
|
||||||
|
PlainElementCodegenNode,
|
||||||
|
ComponentNode
|
||||||
} from '../../src'
|
} from '../../src'
|
||||||
import { ErrorCodes } from '../../src/errors'
|
import { ErrorCodes } from '../../src/errors'
|
||||||
import { transformModel } from '../../src/transforms/vModel'
|
import { transformModel } from '../../src/transforms/vModel'
|
||||||
import { transformElement } from '../../src/transforms/transformElement'
|
import { transformElement } from '../../src/transforms/transformElement'
|
||||||
import { transformExpression } from '../../src/transforms/transformExpression'
|
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||||
|
import { transformFor } from '../../src/transforms/vFor'
|
||||||
|
import { trackSlotScopes } from '../../src/transforms/vSlot'
|
||||||
|
|
||||||
function parseWithVModel(template: string, options: CompilerOptions = {}) {
|
function parseWithVModel(template: string, options: CompilerOptions = {}) {
|
||||||
const ast = parse(template)
|
const ast = parse(template)
|
||||||
|
|
||||||
transform(ast, {
|
transform(ast, {
|
||||||
nodeTransforms: [transformExpression, transformElement],
|
nodeTransforms: [
|
||||||
|
transformFor,
|
||||||
|
transformExpression,
|
||||||
|
trackSlotScopes,
|
||||||
|
transformElement
|
||||||
|
],
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
...options.directiveTransforms,
|
...options.directiveTransforms,
|
||||||
model: transformModel
|
model: transformModel
|
||||||
@ -327,6 +338,39 @@ describe('compiler: transform v-model', () => {
|
|||||||
expect(generate(root, { mode: 'module' }).code).toMatchSnapshot()
|
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', () => {
|
describe('errors', () => {
|
||||||
test('missing expression', () => {
|
test('missing expression', () => {
|
||||||
const onError = jest.fn()
|
const onError = jest.fn()
|
||||||
|
@ -193,11 +193,12 @@ export function buildProps(
|
|||||||
const analyzePatchFlag = ({ key, value }: Property) => {
|
const analyzePatchFlag = ({ key, value }: Property) => {
|
||||||
if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
|
if (key.type === NodeTypes.SIMPLE_EXPRESSION && key.isStatic) {
|
||||||
if (
|
if (
|
||||||
value.type !== NodeTypes.SIMPLE_EXPRESSION ||
|
(value.type === NodeTypes.SIMPLE_EXPRESSION ||
|
||||||
// E.g: <p :foo="1 + 2" />.
|
value.type === NodeTypes.COMPOUND_EXPRESSION) &&
|
||||||
// Do not add prop `foo` to `dynamicPropNames`.
|
isStaticNode(value)
|
||||||
(!value.isStatic && !value.isConstant)
|
|
||||||
) {
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const name = key.content
|
const name = key.content
|
||||||
if (name === 'ref') {
|
if (name === 'ref') {
|
||||||
hasRef = true
|
hasRef = true
|
||||||
@ -208,7 +209,6 @@ export function buildProps(
|
|||||||
} else if (name !== 'key') {
|
} else if (name !== 'key') {
|
||||||
dynamicPropNames.push(name)
|
dynamicPropNames.push(name)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
hasDynamicKeys = true
|
hasDynamicKeys = true
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import { DirectiveTransform } from '../transform'
|
import { DirectiveTransform, TransformContext } from '../transform'
|
||||||
import {
|
import {
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
createObjectProperty,
|
createObjectProperty,
|
||||||
createCompoundExpression,
|
createCompoundExpression,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
Property
|
Property,
|
||||||
|
CompoundExpressionNode,
|
||||||
|
createInterpolation
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import { isMemberExpression } from '../utils'
|
import { isMemberExpression } from '../utils'
|
||||||
|
import { isObject } from '@vue/shared'
|
||||||
|
|
||||||
export const transformModel: DirectiveTransform = (dir, node, context) => {
|
export const transformModel: DirectiveTransform = (dir, node, context) => {
|
||||||
const { exp, arg } = dir
|
const { exp, arg } = dir
|
||||||
@ -38,13 +41,23 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
|||||||
])
|
])
|
||||||
: createSimpleExpression('onUpdate:modelValue', true)
|
: 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 = [
|
const props = [
|
||||||
createObjectProperty(propName, dir.exp!),
|
createObjectProperty(propName, dir.exp!),
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
eventName,
|
eventName,
|
||||||
createCompoundExpression([
|
createCompoundExpression([
|
||||||
`$event => (`,
|
`$event => (`,
|
||||||
...(exp.type === NodeTypes.SIMPLE_EXPRESSION ? [exp] : exp.children),
|
...assignmentChildren,
|
||||||
` = $event)`
|
` = $event)`
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
@ -57,6 +70,30 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
|||||||
return createTransformProps(props)
|
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[] = []) {
|
function createTransformProps(props: Property[] = []) {
|
||||||
return { props, needRuntime: false }
|
return { props, needRuntime: false }
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user