提交 743c8786 编写于 作者: fxy060608's avatar fxy060608

fix(mp-weixin): v-for + key + v-on (question/137217)

上级 39a907e8
......@@ -11,7 +11,8 @@ export interface MiniProgramCompilerOptions {
[name: string]: { name: 'on' | 'bind'; arg: string[] }[]
}
event?: {
format(
key?: boolean
format?(
name: string,
opts: { isCatch?: boolean; isCapture?: boolean; isComponent?: boolean }
): string
......
......@@ -4386,7 +4386,7 @@ var PickerView = /* @__PURE__ */ defineBuiltInComponent({
function getItemIndex(vnode) {
let columnVNodes = columnsRef.value;
{
columnVNodes = columnVNodes.filter((ref) => typeof ref.type !== "symbol");
columnVNodes = columnVNodes.filter((vnode2) => vnode2.type !== vue.Comment);
}
let index2 = columnVNodes.indexOf(vnode);
return index2 !== -1 ? index2 : ColumnsPreRef.value.indexOf(vnode);
......
import { withModifiers, createVNode, getCurrentInstance, ref, defineComponent, openBlock, createElementBlock, provide, computed, watch, onUnmounted, inject, onBeforeUnmount, mergeProps, injectHook, reactive, onActivated, onMounted, nextTick, onBeforeMount, withDirectives, vShow, shallowRef, watchEffect, isVNode, Fragment, markRaw, createTextVNode, onBeforeActivate, onBeforeDeactivate, createBlock, renderList, onDeactivated, createApp, Transition, effectScope, withCtx, KeepAlive, resolveDynamicComponent, createElementVNode, normalizeStyle, renderSlot } from "vue";
import { withModifiers, createVNode, getCurrentInstance, ref, defineComponent, openBlock, createElementBlock, provide, computed, watch, onUnmounted, inject, onBeforeUnmount, mergeProps, injectHook, reactive, onActivated, onMounted, nextTick, onBeforeMount, withDirectives, vShow, shallowRef, watchEffect, isVNode, Fragment, markRaw, Comment, createTextVNode, onBeforeActivate, onBeforeDeactivate, createBlock, renderList, onDeactivated, createApp, Transition, effectScope, withCtx, KeepAlive, resolveDynamicComponent, createElementVNode, normalizeStyle, renderSlot } from "vue";
import { isString, extend, stringifyStyle, parseStringStyle, isPlainObject, isFunction, capitalize, camelize, isArray, hasOwn, isObject, toRawType, makeMap as makeMap$1, isPromise, hyphenate, invokeArrayFns as invokeArrayFns$1 } from "@vue/shared";
import { once, UNI_STORAGE_LOCALE, I18N_JSON_DELIMITERS, passive, initCustomDataset, resolveComponentInstance, addLeadingSlash, invokeArrayFns, resolveOwnerVm, resolveOwnerEl, ON_WXS_INVOKE_CALL_METHOD, normalizeTarget, ON_RESIZE, ON_APP_ENTER_FOREGROUND, ON_APP_ENTER_BACKGROUND, ON_SHOW, ON_HIDE, ON_PAGE_SCROLL, ON_REACH_BOTTOM, EventChannel, SCHEME_RE, DATA_RE, getCustomDataset, LINEFEED, ON_ERROR, callOptions, ON_LAUNCH, PRIMARY_COLOR, removeLeadingSlash, getLen, debounce, ON_LOAD, UniLifecycleHooks, NAVBAR_HEIGHT, parseQuery, ON_UNLOAD, ON_REACH_BOTTOM_DISTANCE, decodedQuery, WEB_INVOKE_APPSERVICE, ON_WEB_INVOKE_APP_SERVICE, updateElementStyle, ON_BACK_PRESS, parseUrl, addFont, scrollTo, RESPONSIVE_MIN_WIDTH, formatDateTime, ON_NAVIGATION_BAR_BUTTON_TAP, ON_NAVIGATION_BAR_SEARCH_INPUT_CLICKED, ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED, ON_NAVIGATION_BAR_SEARCH_INPUT_CHANGED, ON_NAVIGATION_BAR_SEARCH_INPUT_CONFIRMED, ON_PULL_DOWN_REFRESH } from "@dcloudio/uni-shared";
import { initVueI18n, isI18nStr, LOCALE_EN, LOCALE_ES, LOCALE_FR, LOCALE_ZH_HANS, LOCALE_ZH_HANT } from "@dcloudio/uni-i18n";
......@@ -10125,7 +10125,7 @@ var PickerView = /* @__PURE__ */ defineBuiltInComponent({
function getItemIndex(vnode) {
let columnVNodes = columnsRef.value;
{
columnVNodes = columnVNodes.filter((ref2) => typeof ref2.type !== "symbol");
columnVNodes = columnVNodes.filter((vnode2) => vnode2.type !== Comment);
}
let index2 = columnVNodes.indexOf(vnode);
return index2 !== -1 ? index2 : ColumnsPreRef.value.indexOf(vnode);
......
......@@ -37,6 +37,7 @@ export const transformModel: DirectiveTransform = (
if (!baseResult.props.length || node.tagType === ElementTypes.COMPONENT) {
return transformComponentVModel(
baseResult.props,
node,
context
) as unknown as DirectiveTransformResult
}
......@@ -96,7 +97,7 @@ function transformElementVModel(
node: ElementNode,
context: TransformContext
) {
const dirs = transformVModel(props, context, {
const dirs = transformVModel(props, node, context, {
binding: 'value',
event: 'input',
formatEventCode(code) {
......@@ -110,6 +111,7 @@ function transformElementVModel(
inputExp.children[2] = combineVOn(
dirs[1].exp!,
inputExp.children[2] as ExpressionNode,
node,
context
)
dirs.length = 1
......@@ -117,7 +119,7 @@ function transformElementVModel(
const inputDir = findInputDirectiveNode(node.props)
if (inputDir && inputDir.exp) {
// 合并到已有的 input 事件中
inputDir.exp = combineVOn(dirs[1].exp!, inputDir.exp, context)
inputDir.exp = combineVOn(dirs[1].exp!, inputDir.exp, node, context)
dirs.length = 1
}
}
......@@ -205,6 +207,7 @@ function parseVOn(exp: ExpressionNode, context: TransformContext) {
function combineVOn(
exp1: ExpressionNode,
exp2: ExpressionNode,
node: ElementNode,
context: TransformContext
) {
return wrapperVOn(
......@@ -215,16 +218,18 @@ function combineVOn(
parseVOn(exp2, context),
`]`,
]),
node,
context
)
}
function transformComponentVModel(
props: Property[],
node: ElementNode,
context: TransformContext
) {
return {
props: transformVModel(props, context, {
props: transformVModel(props, node, context, {
formatEventCode(code) {
return code
},
......@@ -234,6 +239,7 @@ function transformComponentVModel(
function transformVModel(
props: Property[],
node: ElementNode,
context: TransformContext,
{
binding,
......@@ -273,6 +279,7 @@ function transformVModel(
(onUpdateExpr.type === NodeTypes.JS_CACHE_EXPRESSION
? onUpdateExpr.value
: onUpdateExpr) as ExpressionNode,
node,
context
)
)
......
import { isDirectiveNode } from '@dcloudio/uni-cli-shared'
import {
createCompilerError,
createCompoundExpression,
......@@ -13,8 +14,11 @@ import {
SimpleExpressionNode,
TO_HANDLER_KEY,
DirectiveTransform,
ElementNode,
findProp,
} from '@vue/compiler-core'
import { camelize, toHandlerKey } from '@vue/shared'
import { genExpr } from '..'
import { V_ON } from '../runtimeHelpers'
import { TransformContext } from '../transform'
import { DirectiveTransformResult } from './transformElement'
......@@ -165,11 +169,13 @@ export const transformOn: DirectiveTransform = (
// )
ret.props[0].value = wrapperVOn(
ret.props[0].value as ExpressionNode,
node,
context
)
} else {
ret.props[0].value = wrapperVOn(
ret.props[0].value as ExpressionNode,
node,
context
)
}
......@@ -192,7 +198,11 @@ function isFilterExpr(value: ExpressionNode, context: TransformContext) {
return false
}
export function wrapperVOn(value: ExpressionNode, context: TransformContext) {
export function wrapperVOn(
value: ExpressionNode,
node: ElementNode,
context: TransformContext
) {
if (isBuiltInIdentifier(value)) {
return value
}
......@@ -200,10 +210,22 @@ export function wrapperVOn(value: ExpressionNode, context: TransformContext) {
if (isFilterExpr(value, context)) {
return value
}
const keys: string[] = []
if (context.miniProgram.event?.key && context.inVFor) {
const keyProp = findProp(node, 'key')
// 仅对 v-for 中 item.id 类型做处理,使用索引无需处理,避免引发更多问题
if (keyProp && isDirectiveNode(keyProp) && keyProp.exp) {
const keyCode = genExpr(keyProp.exp)
if (keyCode && keyCode.includes('.')) {
keys.push(',')
keys.push(genExpr(keyProp.exp))
}
}
}
return createCompoundExpression([
`${context.helperString(V_ON)}(`,
value,
...keys,
`)`,
])
}
......@@ -99,6 +99,9 @@ const miniProgram = {
fallbackContent: false,
dynamicSlotNames: true,
},
event: {
key: true,
},
directive: 'qq:',
component: {
dir: COMPONENTS_DIR,
......
......@@ -30,6 +30,9 @@ export const miniProgram: MiniProgramCompilerOptions = {
fallbackContent: false,
dynamicSlotNames: true,
},
event: {
key: true,
},
directive: 'qq:',
component: {
dir: COMPONENTS_DIR,
......
......@@ -5279,10 +5279,17 @@ function getCreateApp() {
}
}
function vOn(value) {
function vOn(value, key) {
const instance = getCurrentInstance();
const name = 'e' + instance.$ei++;
const mpInstance = instance.ctx.$scope;
const ctx = instance.ctx;
// 微信小程序,QQ小程序,当 setData diff 的时候,若事件不主动同步过去,会导致事件绑定不更新,(question/137217)
const extraKey = typeof key !== 'undefined' &&
(ctx.$mpPlatform === 'mp-weixin' || ctx.$mpPlatform === 'mp-qq') &&
(isString(key) || typeof key === 'number')
? '_' + key
: '';
const name = 'e' + instance.$ei++ + extraKey;
const mpInstance = ctx.$scope;
if (!value) {
// remove
delete mpInstance[name];
......@@ -5486,7 +5493,7 @@ function setupDevtoolsPlugin() {
// noop
}
const o = (value) => vOn(value);
const o = (value, key) => vOn(value, key);
const f = (source, renderItem) => vFor(source, renderItem);
const d = (names) => dynamicSlot(names);
const r = (name, props, key) => renderSlot(name, props, key);
......
......@@ -18,7 +18,7 @@ export { setupDevtoolsPlugin } from './devtools'
export { findComponentPropsData, pruneComponentPropsCache } from './renderProps'
export const o: typeof vOn = (value) => vOn(value)
export const o: typeof vOn = (value, key) => vOn(value, key)
export const f: typeof vFor = (
source: any,
renderItem: (...args: any[]) => VForItem
......
import { extend, isArray, isPlainObject, hasOwn, NOOP } from '@vue/shared'
import {
extend,
isArray,
isPlainObject,
hasOwn,
NOOP,
isString,
} from '@vue/shared'
import {
callWithAsyncErrorHandling,
ComponentInternalInstance,
......@@ -13,13 +20,23 @@ interface Invoker {
value: EventValue
}
export function vOn(value: EventValue | undefined) {
export function vOn(value: EventValue | undefined, key?: number | string) {
const instance = getCurrentInstance()! as unknown as {
$ei: number
ctx: { $scope: Record<string, any> }
ctx: { $scope: Record<string, any>; $mpPlatform: UniApp.PLATFORM }
}
const name = 'e' + instance.$ei++
const mpInstance = instance.ctx.$scope
const ctx = instance.ctx
// 微信小程序,QQ小程序,当 setData diff 的时候,若事件不主动同步过去,会导致事件绑定不更新,(question/137217)
const extraKey =
typeof key !== 'undefined' &&
(ctx.$mpPlatform === 'mp-weixin' || ctx.$mpPlatform === 'mp-qq') &&
(isString(key) || typeof key === 'number')
? '_' + key
: ''
const name = 'e' + instance.$ei++ + extraKey
const mpInstance = ctx.$scope
if (!value) {
// remove
delete mpInstance[name]
......
import { assert } from './testUtils'
describe('mp-weixin: transform v-on', () => {
test('basic', () => {
assert(
`<view v-on:click="onClick"/>`,
`<view bindtap="{{a}}"/>`,
`(_ctx, _cache) => {
return { a: _o(_ctx.onClick) }
}`
)
assert(
`<custom v-on:click="onClick"/>`,
`<custom bindclick="{{a}}" u-i="2a9ec0b0-0" bind:__l="__l"/>`,
`(_ctx, _cache) => {
return { a: _o(_ctx.onClick) }
}`
)
})
test('v-for with v-on', () => {
assert(
`<view v-for="item of arr" :key="item._id" @click="show(item)">{{ item.text }}</view>`,
`<view wx:for="{{a}}" wx:for-item="item" wx:key="b" bindtap="{{item.c}}">{{item.a}}</view>`,
`(_ctx, _cache) => {
return { a: _f(_ctx.arr, (item, k0, i0) => { return { a: _t(item.text), b: item._id, c: _o($event => _ctx.show(item), item._id) }; }) }
}`
)
assert(
`<view v-for="item in items" @click="test(item)"/>`,
`<view wx:for="{{a}}" wx:for-item="item" bindtap="{{item.a}}"/>`,
`(_ctx, _cache) => {
return { a: _f(_ctx.items, (item, k0, i0) => { return { a: _o($event => _ctx.test(item)) }; }) }
}`
)
assert(
`<view v-for="(item,index) in items" :key="index" @click="test(item)"/>`,
`<view wx:for="{{a}}" wx:for-item="item" wx:key="a" bindtap="{{item.b}}"/>`,
`(_ctx, _cache) => {
return { a: _f(_ctx.items, (item, index, i0) => { return { a: index, b: _o($event => _ctx.test(item)) }; }) }
}`
)
})
})
......@@ -76,6 +76,9 @@ const miniProgram = {
fallbackContent: false,
dynamicSlotNames: true,
},
event: {
key: true,
},
directive: 'wx:',
lazyElement: {
canvas: [{ name: 'bind', arg: ['canvas-id', 'id'] }],
......
......@@ -32,6 +32,9 @@ export const miniProgram: MiniProgramCompilerOptions = {
fallbackContent: false,
dynamicSlotNames: true,
},
event: {
key: true,
},
directive: 'wx:',
lazyElement: {
canvas: [{ name: 'bind', arg: ['canvas-id', 'id'] }],
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册