提交 233fe0c6 编写于 作者: fxy060608's avatar fxy060608

refactor(uvue): vModel

上级 8152b3bf
......@@ -3,16 +3,14 @@
exports[`compiler: transform v-model compound expression (with prefixIdentifiers) 1`] = `
"createElementVNode("input", utsMapOf({
modelValue: _ctx.model[_ctx.index],
onInput: ($event: InputEvent): any => {_ctx.model[_ctx.index] = $event.detail.value;
return $event.detail.value;}
onInput: ($event: InputEvent) => {(_ctx.model[_ctx.index]) = $event.detail.value}
}), null, 40 /* PROPS, NEED_HYDRATION */, ["modelValue", "onInput"])"
`;
exports[`compiler: transform v-model compound expression 1`] = `
"createElementVNode("input", utsMapOf({
modelValue: model[index],
onInput: ($event: InputEvent): any => {model[index] = $event.detail.value;
return $event.detail.value;}
onInput: ($event: InputEvent) => {(model[index]) = $event.detail.value}
}), null, 40 /* PROPS, NEED_HYDRATION */, ["modelValue", "onInput"])"
`;
......@@ -23,42 +21,39 @@ exports[`compiler: transform v-model simple expression (with multilines) 1`] = `
.
foo
,
onInput: ($event: InputEvent): any => {
onInput: ($event: InputEvent) => {(
model
.
foo
= $event.detail.value;
return $event.detail.value;}
) = $event.detail.value}
}), null, 40 /* PROPS, NEED_HYDRATION */, ["modelValue", "onInput"])"
`;
exports[`compiler: transform v-model simple expression (with prefixIdentifiers) 1`] = `
"createElementVNode("input", utsMapOf({
modelValue: _ctx.model,
onInput: ($event: InputEvent): any => {_ctx.model = $event.detail.value;
return $event.detail.value;}
onInput: ($event: InputEvent) => {(_ctx.model) = $event.detail.value}
}), null, 40 /* PROPS, NEED_HYDRATION */, ["modelValue", "onInput"])"
`;
exports[`compiler: transform v-model simple expression 1`] = `
"createElementVNode("input", utsMapOf({
modelValue: model,
onInput: ($event: InputEvent): any => {model = $event.detail.value;
return $event.detail.value;}
onInput: ($event: InputEvent) => {(model) = $event.detail.value}
}), null, 40 /* PROPS, NEED_HYDRATION */, ["modelValue", "onInput"])"
`;
exports[`compiler: transform v-model with argument 1`] = `
"createElementVNode("input", utsMapOf({
"foo-value": model,
"onUpdate:fooValue": $event => {(model) = $event}
"onUpdate:fooValue": ($event: InputEvent) => {(model) = $event.detail.value}
}), null, 40 /* PROPS, NEED_HYDRATION */, ["foo-value", "onUpdate:fooValue"])"
`;
exports[`compiler: transform v-model with dynamic argument (with prefixIdentifiers) 1`] = `
"createElementVNode("input", normalizeProps(utsMapOf({
[_ctx.value]: _ctx.model,
["onUpdate:" + _ctx.value]: $event => {(_ctx.model) = $event}
["onUpdate:" + _ctx.value]: ($event: InputEvent) => {(_ctx.model) = $event.detail.value}
})), null, 16 /* FULL_PROPS */)"
`;
......
......@@ -49,8 +49,7 @@ describe('compiler: transform v-model', () => {
`<input v-model="model" />`,
`createElementVNode("input", utsMapOf({
modelValue: _ctx.model,
onInput: ($event: InputEvent): any => {_ctx.model = $event.detail.value;
return $event.detail.value;}
onInput: ($event: InputEvent) => {(_ctx.model) = $event.detail.value}
}), null, 40 /* PROPS, NEED_HYDRATION */, ["modelValue", "onInput"])`
)
})
......@@ -63,11 +62,10 @@ return $event.detail.value;}
modelValue: \n_ctx.model.
foo
,
onInput: ($event: InputEvent): any => {
onInput: ($event: InputEvent) => {(
_ctx.model.
foo
= $event.detail.value;
return $event.detail.value;}
) = $event.detail.value}
}), null, 40 /* PROPS, NEED_HYDRATION */, ["modelValue", "onInput"])`
)
})
......@@ -77,8 +75,7 @@ return $event.detail.value;}
`<input v-model="model[index]" />`,
`createElementVNode(\"input\", utsMapOf({
modelValue: _ctx.model[_ctx.index],
onInput: ($event: InputEvent): any => {_ctx.model[_ctx.index] = $event.detail.value;
return $event.detail.value;}
onInput: ($event: InputEvent) => {(_ctx.model[_ctx.index]) = $event.detail.value}
}), null, 40 /* PROPS, NEED_HYDRATION */, [\"modelValue\", \"onInput\"])`
)
})
......@@ -107,8 +104,7 @@ return $event.detail.value;}
`<input v-model.lazy="model" />`,
`createElementVNode(\"input\", utsMapOf({
modelValue: _ctx.model,
onBlur: ($event: InputBlurEvent): any => {_ctx.model = $event.detail.value;
return $event.detail.value;}
onBlur: ($event: InputBlurEvent) => {(_ctx.model) = $event.detail.value}
}), null, 40 /* PROPS, NEED_HYDRATION */, [\"modelValue\", \"onBlur\"])`
)
})
......@@ -117,8 +113,7 @@ return $event.detail.value;}
`<input v-model.number="model" />`,
`createElementVNode(\"input\", utsMapOf({
modelValue: _ctx.model,
onInput: ($event: InputEvent): any => {_ctx.model = _looseToNumber($event.detail.value);
return _looseToNumber($event.detail.value);}
onInput: ($event: InputEvent) => {(_ctx.model) = looseToNumber($event.detail.value)}
}), null, 40 /* PROPS, NEED_HYDRATION */, [\"modelValue\", \"onInput\"])`
)
})
......@@ -127,8 +122,7 @@ return _looseToNumber($event.detail.value);}
`<input v-model.trim="model" />`,
`createElementVNode(\"input\", utsMapOf({
modelValue: _ctx.model,
onInput: ($event: InputEvent): any => {_ctx.model = $event.detail.value.trim();
return $event.detail.value.trim();}
onInput: ($event: InputEvent) => {(_ctx.model) = $event.detail.value.trim()}
}), null, 40 /* PROPS, NEED_HYDRATION */, [\"modelValue\", \"onInput\"])`
)
})
......@@ -145,8 +139,8 @@ return $event.detail.value.trim();}
assert(
`<my-input v-model="(obj.str as string)" />`,
`createVNode(_component_my_input, utsMapOf({
modelValue: (_ctx.obj.str),
\"onUpdate:modelValue\": ($event: string) => {((_ctx.obj.str)) = $event}
modelValue: _ctx.obj.str,
\"onUpdate:modelValue\": ($event: string) => {(_ctx.obj.str) = $event}
}), null, 8 /* PROPS */, [\"modelValue\", \"onUpdate:modelValue\"])`
)
})
......@@ -175,13 +169,12 @@ return $event.detail.value.trim();}
},
value: {
children: [
'($event: InputEvent): any => {',
'($event: InputEvent) => {(',
{
content: 'model',
isStatic: false,
},
` = $event.detail.value;
return $event.detail.value;}`,
`) = $event.detail.value}`,
],
},
})
......@@ -215,13 +208,12 @@ return $event.detail.value;}`,
},
value: {
children: [
'($event: InputEvent): any => {',
'($event: InputEvent) => {(',
{
content: '_ctx.model',
isStatic: false,
},
` = $event.detail.value;
return $event.detail.value;}`,
`) = $event.detail.value}`,
],
},
})
......@@ -254,13 +246,12 @@ return $event.detail.value;}`,
},
value: {
children: [
'($event: InputEvent): any => {',
'($event: InputEvent) => {(',
{
content: '\n model\n.\nfoo \n',
isStatic: false,
},
` = $event.detail.value;
return $event.detail.value;}`,
`) = $event.detail.value}`,
],
},
})
......@@ -292,13 +283,12 @@ return $event.detail.value;}`,
},
value: {
children: [
'($event: InputEvent): any => {',
'($event: InputEvent) => {(',
{
content: 'model[index]',
isStatic: false,
},
` = $event.detail.value;
return $event.detail.value;}`,
`) = $event.detail.value}`,
],
},
})
......@@ -342,7 +332,7 @@ return $event.detail.value;}`,
},
value: {
children: [
'($event: InputEvent): any => {',
'($event: InputEvent) => {(',
{
children: [
{
......@@ -357,8 +347,7 @@ return $event.detail.value;}`,
']',
],
},
` = $event.detail.value;
return $event.detail.value;}`,
`) = $event.detail.value}`,
],
},
})
......@@ -389,12 +378,12 @@ return $event.detail.value;}`,
},
value: {
children: [
'$event => {(',
'($event: InputEvent) => {(',
{
content: 'model',
isStatic: false,
},
`) = $event}`,
`) = $event.detail.value}`,
],
},
})
......@@ -491,12 +480,12 @@ return $event.detail.value;}`,
},
value: {
children: [
'$event => {(',
'($event: InputEvent) => {(',
{
content: '_ctx.model',
isStatic: false,
},
`) = $event}`,
`) = $event.detail.value}`,
],
},
},
......
......@@ -86,6 +86,19 @@ export interface TransformOptions
extends SharedTransformCodegenOptions,
ErrorHandlingOptions {
rootDir?: string
/**
* Cache v-on handlers to avoid creating new inline functions on each render,
* also avoids the need for dynamically patching the handlers by wrapping it.
* e.g `@click="foo"` by default is compiled to `{ onClick: foo }`. With this
* option it's compiled to:
* ```js
* { onClick: _cache[0] || (_cache[0] = e => _ctx.foo(e)) }
* ```
* - Requires "prefixIdentifiers" to be enabled because it relies on scope
* analysis to determine if a handler is safe to cache.
* @default false
*/
cacheHandlers?: boolean
/**
* An array of node transforms to be applied to every AST node.
*/
......
......@@ -119,6 +119,7 @@ export function createTransformContext(
rootDir = '',
targetLanguage = 'kotlin',
filename = '',
cacheHandlers = false,
prefixIdentifiers = false,
nodeTransforms = [],
directiveTransforms = {},
......@@ -138,6 +139,7 @@ export function createTransformContext(
rootDir,
targetLanguage,
selfName: nameMatch && capitalize(camelize(nameMatch[1])),
cacheHandlers,
prefixIdentifiers,
bindingMetadata,
inline,
......
import { camelize, isString } from '@vue/shared'
import {
transformModel as baseTransform,
BindingTypes,
CompoundExpressionNode,
ConstantTypes,
createCompoundExpression,
createObjectProperty,
createSimpleExpression,
DirectiveNode,
DirectiveTransform,
ElementTypes,
ExpressionNode,
hasScopeRef,
IS_REF,
isMemberExpression,
isSimpleIdentifier,
isStaticExp,
NodeTypes,
Property,
} from '@vue/compiler-core'
import { isString } from '@vue/shared'
import { createCompilerError } from '../errors'
import { isCompoundExpressionNode } from '@dcloudio/uni-cli-shared'
const tags = ['input', 'textarea']
import { DirectiveTransform } from '../transform'
import { createCompilerError, ErrorCodes } from '../errors'
const INPUT_TAGS = ['input', 'textarea']
const AS = ' as '
export const transformModel: DirectiveTransform = (dir, node, context) => {
// 组件 v-model 绑定了复杂表达式,且没有手动 as 类型
if (
node.tagType === ElementTypes.COMPONENT &&
(dir.exp as CompoundExpressionNode)?.children?.length > 1 &&
!dir.loc.source.includes(' as ')
!dir.loc.source.includes(AS)
) {
context.onError(
createCompilerError(100, dir.loc, {
......@@ -25,47 +39,184 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
})
)
}
const result = baseTransform(dir, node, context)
// 将 input,textarea 的 onUpdate:modelValue 事件转换为 onInput
if (tags.includes(node.tag) && result.props.length >= 2) {
handleInputEvent(result, dir)
const { exp, arg } = dir
if (!exp) {
context.onError(
createCompilerError(ErrorCodes.X_V_MODEL_NO_EXPRESSION, dir.loc)
)
return createTransformProps()
}
handleUpdateExpression(result.props)
return result
}
function handleInputEvent(result: any, dir: DirectiveNode) {
const key = result.props[1].key
let value = result.props[1].value
if (value.type === NodeTypes.JS_CACHE_EXPRESSION) {
value = value.value
let rawExp = exp.loc.source
let expType = ''
if (isCompoundExpressionNode(exp)) {
if (rawExp.includes(AS)) {
// 目前简单处理(a as string)
if (rawExp.startsWith('(') && rawExp.endsWith(')')) {
rawExp = rawExp.slice(1, -1)
}
const parts = rawExp.split(AS)
rawExp = parts[0].trim()
expType = parts[1].trim()
let len = exp.children.length - 1
exp.children = exp.children.filter((child, index) => {
if (
isString(child) &&
(child.includes(AS) ||
(index === 0 && child === '(') ||
(index === len && child === ')'))
) {
return false
}
return true
})
}
}
const expString =
exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : rawExp
// im SFC <script setup> inline mode, the exp may have been transformed into
// _unref(exp)
const bindingType = context.bindingMetadata[rawExp]
// check props
if (
bindingType === BindingTypes.PROPS ||
bindingType === BindingTypes.PROPS_ALIASED
) {
context.onError(createCompilerError(ErrorCodes.X_V_MODEL_ON_PROPS, exp.loc))
return createTransformProps()
}
const maybeRef =
context.inline &&
(bindingType === BindingTypes.SETUP_LET ||
bindingType === BindingTypes.SETUP_REF ||
bindingType === BindingTypes.SETUP_MAYBE_REF)
if (
isStaticExp(key) &&
key.content === 'onUpdate:modelValue' &&
value.type === NodeTypes.COMPOUND_EXPRESSION
!expString.trim() ||
(!isMemberExpression(expString, context as any) && !maybeRef)
) {
key.content = getEventName(dir)
// 调整函数类型及返回表达式,适配 uts
value.children = value.children.map((child: any) => {
if (isString(child)) {
if (child === '$event => ((') {
return `($event: ${getEventParamsType(dir)}): any => {`
} else if (child === ') = $event)') {
return ` = ${
withNumber(dir) ? '_looseToNumber(' : ''
}$event.detail.value${withTrim(dir) ? '.trim()' : ''}${
withNumber(dir) ? ')' : ''
};\nreturn ${
withNumber(dir) ? '_looseToNumber(' : ''
}$event.detail.value${withTrim(dir) ? '.trim()' : ''}${
withNumber(dir) ? ')' : ''
};}`
}
}
return child
})
context.onError(
createCompilerError(ErrorCodes.X_V_MODEL_MALFORMED_EXPRESSION, exp.loc)
)
return createTransformProps()
}
if (
context.prefixIdentifiers &&
isSimpleIdentifier(expString) &&
context.identifiers[expString]
) {
context.onError(
createCompilerError(ErrorCodes.X_V_MODEL_ON_SCOPE_VARIABLE, exp.loc)
)
return createTransformProps()
}
const isInputElement = INPUT_TAGS.includes(node.tag)
const propName = arg ? arg : createSimpleExpression('modelValue', true)
let eventName = arg
? isStaticExp(arg)
? `onUpdate:${camelize(arg.content)}`
: createCompoundExpression(['"onUpdate:" + ', arg])
: `onUpdate:modelValue`
if (isInputElement && eventName === 'onUpdate:modelValue') {
eventName = getEventName(dir)
}
const eventType = isInputElement ? getEventParamsType(dir) : expType
let eventValue = isInputElement ? `$event.detail.value` : `$event`
if (withTrim(dir)) {
eventValue = `${eventValue}.trim()`
}
if (withNumber(dir)) {
eventValue = `looseToNumber(${eventValue})`
}
let assignmentExp: ExpressionNode
const eventArg = eventType ? `($event: ${eventType})` : `$event`
if (maybeRef) {
if (bindingType === BindingTypes.SETUP_REF) {
// v-model used on known ref.
assignmentExp = createCompoundExpression([
`${eventArg} => {(`,
createSimpleExpression(rawExp, false, exp.loc),
`).value = ${eventValue}}`,
])
} else {
// v-model used on a potentially ref binding in <script setup> inline mode.
// the assignment needs to check whether the binding is actually a ref.
const altAssignment =
bindingType === BindingTypes.SETUP_LET
? `${rawExp} = ${eventValue}`
: `null`
assignmentExp = createCompoundExpression([
`${eventArg} => {${context.helperString(IS_REF)}(${rawExp}) ? (`,
createSimpleExpression(rawExp, false, exp.loc),
`).value = ${eventValue} : ${altAssignment}}`,
])
}
} else {
assignmentExp = createCompoundExpression([
`${eventArg} => {(`,
exp,
`) = ${eventValue}}`,
])
}
const props = [
// modelValue: foo
createObjectProperty(propName, exp),
// "onUpdate:modelValue": $event => (foo = $event)
createObjectProperty(eventName, assignmentExp),
]
// cache v-model handler if applicable (when it doesn't refer any scope vars)
if (
context.prefixIdentifiers &&
!context.inVOnce &&
context.cacheHandlers &&
!hasScopeRef(exp, context.identifiers)
) {
props[1].value = context.cache(props[1].value)
}
// modelModifiers: { foo: true, "bar-baz": true }
if (dir.modifiers.length && node.tagType === ElementTypes.COMPONENT) {
const modifiers = dir.modifiers
.map((m) => (isSimpleIdentifier(m) ? m : JSON.stringify(m)) + `: true`)
.join(`, `)
const modifiersKey = arg
? isStaticExp(arg)
? `${arg.content}Modifiers`
: createCompoundExpression([arg, ' + "Modifiers"'])
: `modelModifiers`
props.push(
createObjectProperty(
modifiersKey,
createSimpleExpression(
`{ ${modifiers} }`,
false,
dir.loc,
ConstantTypes.CAN_HOIST
)
)
)
}
return createTransformProps(props)
}
function createTransformProps(props: Property[] = []) {
return { props }
}
function getEventName(dir: DirectiveNode): string {
......@@ -87,45 +238,3 @@ function withNumber(dir: DirectiveNode): boolean {
function withTrim(dir: DirectiveNode): boolean {
return dir.modifiers.includes('trim')
}
/*
["onUpdate:modelValue", $event => ((_ctx.modelValue1) = $event)] => ["onUpdate:modelValue", $event => { (_ctx.modelValue1) = $event }]
["onUpdate:modelValue", $event => ((_ctx.modelValue1 as string) = $event)] => ["onUpdate:modelValue", ($event: string) => { (_ctx.modelValue1) = $event }]
*/
function handleUpdateExpression(props: any[]) {
let variableType = ''
props.forEach((prop: any) => {
if (prop.value.children) {
for (let i = 0; i < prop.value.children.length; i++) {
const child = prop.value.children[i]
if (isString(child)) {
if (child.includes('as ')) {
variableType = child.split('as ')[1]
if (variableType.endsWith(')')) {
variableType = variableType.slice(0, -1)
prop.value.children[i] = ')'
} else {
prop.value.children.splice(i, 1)
i--
}
}
if (child.includes('$event =>') && variableType) {
prop.value.children[i] = child.replace(
'$event',
`($event: ${variableType})`
)
}
if (child.includes('=> ((')) {
prop.value.children[i] = prop.value.children[i].replace(
'=> ((',
'=> {('
)
}
if (child.includes('= $event)')) {
prop.value.children[i] = child.replace('= $event)', '= $event}')
}
}
}
}
})
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册