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,11 +193,12 @@ 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) | ||||
|       ) { | ||||
|         return | ||||
|       } | ||||
|       const name = key.content | ||||
|       if (name === 'ref') { | ||||
|         hasRef = true | ||||
| @ -208,7 +209,6 @@ export function buildProps( | ||||
|       } 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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user