]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler-core/v-model): avoid patching v-model handler when possible
authorEvan You <yyx990803@gmail.com>
Wed, 16 Oct 2019 17:56:00 +0000 (13:56 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 16 Oct 2019 19:35:04 +0000 (15:35 -0400)
packages/compiler-core/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
packages/compiler-core/__tests__/transforms/vModel.spec.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-core/src/transforms/vModel.ts

index ea11cf7151991790709bb0dfc188274416063a95..48c22aef59c4c7d0b1ba4a6437b2e744677abba9 100644 (file)
@@ -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\\"]))
 }"
 `;
 
index 3eed83cce1628a8292f2bafd61af5ae9f6431088..42f3d8c524d50ef62ec06897310de50f944733b5 100644 (file)
@@ -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()
index 571eb89d5b3db07a4614da7b17ce71c0c67a72ac..183a1f987849d555382186ed6e98eb265e83786d 100644 (file)
@@ -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
index 86857ce37fd5f9072e498335fe6411afc1a187e2..00dcc84cf8a50955cdd74d0206adabfbcc7c04d8 100644 (file)
@@ -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 }
 }