vModel.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import { DirectiveTransform } from '../transform'
  2. import {
  3. createSimpleExpression,
  4. createObjectProperty,
  5. createCompoundExpression,
  6. NodeTypes,
  7. Property,
  8. ElementTypes,
  9. ExpressionNode,
  10. ConstantTypes
  11. } from '../ast'
  12. import { createCompilerError, ErrorCodes } from '../errors'
  13. import {
  14. isMemberExpression,
  15. isSimpleIdentifier,
  16. hasScopeRef,
  17. isStaticExp
  18. } from '../utils'
  19. import { IS_REF } from '../runtimeHelpers'
  20. import { BindingTypes } from '../options'
  21. export const transformModel: DirectiveTransform = (dir, node, context) => {
  22. const { exp, arg } = dir
  23. if (!exp) {
  24. context.onError(
  25. createCompilerError(ErrorCodes.X_V_MODEL_NO_EXPRESSION, dir.loc)
  26. )
  27. return createTransformProps()
  28. }
  29. const rawExp = exp.loc.source
  30. const expString =
  31. exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : rawExp
  32. // im SFC <script setup> inline mode, the exp may have been transformed into
  33. // _unref(exp)
  34. const bindingType = context.bindingMetadata[rawExp]
  35. const maybeRef =
  36. !__BROWSER__ &&
  37. context.inline &&
  38. bindingType &&
  39. bindingType !== BindingTypes.SETUP_CONST
  40. if (!expString.trim() || (!isMemberExpression(expString) && !maybeRef)) {
  41. context.onError(
  42. createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc)
  43. )
  44. return createTransformProps()
  45. }
  46. if (
  47. !__BROWSER__ &&
  48. context.prefixIdentifiers &&
  49. isSimpleIdentifier(expString) &&
  50. context.identifiers[expString]
  51. ) {
  52. context.onError(
  53. createCompilerError(ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE, exp.loc)
  54. )
  55. return createTransformProps()
  56. }
  57. const propName = arg ? arg : createSimpleExpression('modelValue', true)
  58. const eventName = arg
  59. ? isStaticExp(arg)
  60. ? `onUpdate:${arg.content}`
  61. : createCompoundExpression(['"onUpdate:" + ', arg])
  62. : `onUpdate:modelValue`
  63. let assignmentExp: ExpressionNode
  64. const eventArg = context.isTS ? `($event: any)` : `$event`
  65. if (maybeRef) {
  66. if (bindingType === BindingTypes.SETUP_REF) {
  67. // v-model used on known ref.
  68. assignmentExp = createCompoundExpression([
  69. `${eventArg} => (`,
  70. createSimpleExpression(rawExp, false, exp.loc),
  71. `.value = $event)`
  72. ])
  73. } else {
  74. // v-model used on a potentially ref binding in <script setup> inline mode.
  75. // the assignment needs to check whether the binding is actually a ref.
  76. const altAssignment =
  77. bindingType === BindingTypes.SETUP_LET ? `${rawExp} = $event` : `null`
  78. assignmentExp = createCompoundExpression([
  79. `${eventArg} => (${context.helperString(IS_REF)}(${rawExp}) ? `,
  80. createSimpleExpression(rawExp, false, exp.loc),
  81. `.value = $event : ${altAssignment})`
  82. ])
  83. }
  84. } else {
  85. assignmentExp = createCompoundExpression([
  86. `${eventArg} => (`,
  87. exp,
  88. ` = $event)`
  89. ])
  90. }
  91. const props = [
  92. // modelValue: foo
  93. createObjectProperty(propName, dir.exp!),
  94. // "onUpdate:modelValue": $event => (foo = $event)
  95. createObjectProperty(eventName, assignmentExp)
  96. ]
  97. // cache v-model handler if applicable (when it doesn't refer any scope vars)
  98. if (
  99. !__BROWSER__ &&
  100. context.prefixIdentifiers &&
  101. context.cacheHandlers &&
  102. !hasScopeRef(exp, context.identifiers)
  103. ) {
  104. props[1].value = context.cache(props[1].value)
  105. }
  106. // modelModifiers: { foo: true, "bar-baz": true }
  107. if (dir.modifiers.length && node.tagType === ElementTypes.COMPONENT) {
  108. const modifiers = dir.modifiers
  109. .map(m => (isSimpleIdentifier(m) ? m : JSON.stringify(m)) + `: true`)
  110. .join(`, `)
  111. const modifiersKey = arg
  112. ? isStaticExp(arg)
  113. ? `${arg.content}Modifiers`
  114. : createCompoundExpression([arg, ' + "Modifiers"'])
  115. : `modelModifiers`
  116. props.push(
  117. createObjectProperty(
  118. modifiersKey,
  119. createSimpleExpression(
  120. `{ ${modifiers} }`,
  121. false,
  122. dir.loc,
  123. ConstantTypes.CAN_HOIST
  124. )
  125. )
  126. )
  127. }
  128. return createTransformProps(props)
  129. }
  130. function createTransformProps(props: Property[] = []) {
  131. return { props }
  132. }