From 8b0dd0b4b4a5759e74f368aae05cc8349fd7610e Mon Sep 17 00:00:00 2001 From: chouchouji <1305974212@qq.com> Date: Mon, 28 Jul 2025 16:30:41 +0800 Subject: [PATCH] =?UTF-8?q?fix(mp):=20=E4=BF=AE=E5=A4=8D=20v-if=20?= =?UTF-8?q?=E5=92=8C=20v-slot=20=E5=90=8C=E6=97=B6=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=97=B6=20v-if=20=E4=B8=8D=E8=B5=B7=E4=BD=9C=E7=94=A8?= =?UTF-8?q?=E7=9A=84=20Bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../uni-mp-alipay/__tests__/vSlot.spec.ts | 4 +- packages/uni-mp-baidu/__tests__/vSlot.spec.ts | 4 +- .../uni-mp-compiler/__tests__/vSlot.spec.ts | 122 +++++++++++++++++- .../uni-mp-compiler/src/transforms/vSlot.ts | 118 ++++++++++++++++- .../uni-mp-core/src/runtime/componentProps.ts | 8 +- 5 files changed, 243 insertions(+), 13 deletions(-) diff --git a/packages/uni-mp-alipay/__tests__/vSlot.spec.ts b/packages/uni-mp-alipay/__tests__/vSlot.spec.ts index e3e377dc89d..6c878ecff0c 100644 --- a/packages/uni-mp-alipay/__tests__/vSlot.spec.ts +++ b/packages/uni-mp-alipay/__tests__/vSlot.spec.ts @@ -57,9 +57,9 @@ describe('mp-alipay: transform v-slot', () => { test('v-if + scoped slots', () => { assert( ``, - `{{slotProps.a}}`, + `{{slotProps.a}}`, `(_ctx, _cache) => { - return _e({ a: _ctx.ok }, _ctx.ok ? { b: _w((slotProps, s0, i0) => { return { a: _t(slotProps.item), b: i0, c: s0 }; }, { name: 'd', path: 'b', vueId: '2a9ec0b0-0' }) } : {}) + return _e({ a: _ctx.ok }, _ctx.ok ? { b: _w((slotProps, s0, i0) => { return { a: _t(slotProps.item), b: i0, c: s0 }; }, { name: 'd', path: 'b', vueId: '2a9ec0b0-0' }) } : {}, { c: [_ctx.ok ? 'd' : ''] }) }` ) }) diff --git a/packages/uni-mp-baidu/__tests__/vSlot.spec.ts b/packages/uni-mp-baidu/__tests__/vSlot.spec.ts index 2adbc2ea5c5..f0253fc2569 100644 --- a/packages/uni-mp-baidu/__tests__/vSlot.spec.ts +++ b/packages/uni-mp-baidu/__tests__/vSlot.spec.ts @@ -57,9 +57,9 @@ describe('compiler: transform v-slot', () => { test('v-if + scoped slots', () => { assert( ``, - `{{slotProps.b}}`, + `{{slotProps.b}}`, `(_ctx, _cache) => { - return _e({ a: _ctx.ok }, _ctx.ok ? { b: _w((slotProps, s0, i0) => { return { a: i0, b: _t(slotProps.item) }; }, { name: 'd', path: 'b', vueId: '2a9ec0b0-0' }) } : {}) + return _e({ a: _ctx.ok }, _ctx.ok ? { b: _w((slotProps, s0, i0) => { return { a: i0, b: _t(slotProps.item) }; }, { name: 'd', path: 'b', vueId: '2a9ec0b0-0' }) } : {}, { c: [_ctx.ok ? 'd' : ''] }) }` ) }) diff --git a/packages/uni-mp-compiler/__tests__/vSlot.spec.ts b/packages/uni-mp-compiler/__tests__/vSlot.spec.ts index 5e451fda84f..3b301a1ee5c 100644 --- a/packages/uni-mp-compiler/__tests__/vSlot.spec.ts +++ b/packages/uni-mp-compiler/__tests__/vSlot.spec.ts @@ -58,9 +58,9 @@ describe('compiler: transform v-slot', () => { ) assert( ``, - ``, + ``, `(_ctx, _cache) => { - return _e({ a: _ctx.ok }, _ctx.ok ? {} : {}) + return _e({ a: _ctx.ok }, _ctx.ok ? {} : {}, { b: [_ctx.ok ? 'body' : ''] }) }` ) }) @@ -88,9 +88,9 @@ describe('compiler: transform v-slot', () => { test('v-if + scoped slots', () => { assert( ``, - `{{slotProps.a}}`, + `{{slotProps.a}}`, `(_ctx, _cache) => { - return _e({ a: _ctx.ok }, _ctx.ok ? { b: _w((slotProps, s0, i0) => { return { a: _t(slotProps.item), b: i0, c: s0 }; }, { name: 'd', path: 'b', vueId: '2a9ec0b0-0' }) } : {}) + return _e({ a: _ctx.ok }, _ctx.ok ? { b: _w((slotProps, s0, i0) => { return { a: _t(slotProps.item), b: i0, c: s0 }; }, { name: 'd', path: 'b', vueId: '2a9ec0b0-0' }) } : {}, { c: [_ctx.ok ? 'd' : ''] }) }` ) }) @@ -183,3 +183,117 @@ describe('should remove template when it has no any child nodes or all of its ch }` ) }) + +describe('v-slot + v-if / v-else-if / v-else', () => { + assert( + ``, + `hello`, + `(_ctx, _cache) => { + return _e({ a: _ctx.a }, _ctx.a ? {} : {}, { b: [_ctx.a ? 'header' : ''] }) +}` + ) + + assert( + ``, + `hello`, + `(_ctx, _cache) => { + return _e({ a: _ctx.a }, _ctx.a ? {} : {}, { b: [_ctx.a ? 'd' : ''] }) +}` + ) + + assert( + ``, + `hellohello`, + `(_ctx, _cache) => { + return _e({ a: _ctx.a }, _ctx.a ? {} : {}, { b: [!_ctx.a ? 'footer' : '', _ctx.a ? 'd' : ''] }) +}` + ) + + assert( + ``, + `hellohello`, + `(_ctx, _cache) => { + return _e({ a: _ctx.a }, _ctx.a ? {} : {}, { b: _ctx.b }, _ctx.b ? {} : {}, { c: [_ctx.a ? 'header' : '', _ctx.b ? 'footer' : ''] }) +}` + ) + + assert( + ``, + `hellohello`, + `(_ctx, _cache) => { + return _e({ a: _ctx.a }, _ctx.a ? {} : {}, { b: _ctx.b }, _ctx.b ? {} : {}, { c: [_ctx.a ? 'header' : '', _ctx.b ? 'd' : ''] }) +}` + ) + + assert( + ``, + `hellohello`, + `(_ctx, _cache) => { + return _e({ a: _ctx.a }, _ctx.a ? {} : {}, { b: [_ctx.a ? 'header' : '', !_ctx.a ? 'footer' : ''] }) +}` + ) + + assert( + ``, + `hellohello`, + `(_ctx, _cache) => { + return _e({ a: _ctx.a }, _ctx.a ? {} : {}, { b: [_ctx.a ? 'header' : '', !_ctx.a ? 'd' : ''] }) +}` + ) + + assert( + ``, + `hellohello`, + `(_ctx, _cache) => { + return _e({ a: _ctx.a }, _ctx.a ? {} : _ctx.b ? {} : {}, { b: _ctx.b, c: [_ctx.a ? 'header' : '', !_ctx.a && _ctx.b ? 'footer' : ''] }) +}` + ) + + assert( + ``, + `hellohellohello`, + `(_ctx, _cache) => { + return _e({ a: _ctx.a }, _ctx.a ? {} : _ctx.b ? {} : _ctx.c ? {} : {}, { b: _ctx.b, c: _ctx.c, d: [_ctx.a ? 'header' : '', !_ctx.a && _ctx.b ? 'footer' : '', !_ctx.a && !_ctx.b && _ctx.c ? 'header2' : ''] }) +}` + ) + + assert( + ``, + `hellohellohello`, + `(_ctx, _cache) => { + return _e({ a: _ctx.a }, _ctx.a ? {} : _ctx.b ? {} : {}, { b: _ctx.b, c: _ctx.c }, _ctx.c ? {} : {}, { d: [_ctx.a ? 'header' : '', !_ctx.a && _ctx.b ? 'footer' : '', _ctx.c ? 'header2' : ''] }) +}` + ) + + assert( + ``, + `hellohellohello`, + `(_ctx, _cache) => { + return _e({ a: _ctx.a }, _ctx.a ? {} : _ctx.b ? {} : {}, { b: _ctx.b, c: [_ctx.a ? 'header' : '', !_ctx.a && _ctx.b ? 'footer' : '', !_ctx.a && !_ctx.b ? 'footer2' : ''] }) +}` + ) + + assert( + ``, + `hellohellohellohello`, + `(_ctx, _cache) => { + return _e({ a: _ctx.a }, _ctx.a ? {} : _ctx.b ? {} : _ctx.c ? {} : {}, { b: _ctx.b, c: _ctx.c, d: [_ctx.a ? 'header' : '', !_ctx.a && _ctx.b ? 'footer' : '', !_ctx.a && !_ctx.b && _ctx.c ? 'footer3' : '', !_ctx.a && !_ctx.b && !_ctx.c ? 'footer2' : ''] }) +}` + ) + + assert( + ``, + `hello`, + `(_ctx, _cache) => { + return _e({ a: _ctx.a }, _ctx.a ? { b: _d(_ctx.header) } : {}, { c: _d([_ctx.a ? _ctx.header : ""]) }) +}` + ) + + assert( + ``, + `hellohello`, + `(_ctx, _cache) => { + return _e({ a: _ctx.a }, _ctx.a ? { b: _d(_ctx.header) } : {}, { c: _d([_ctx.a ? _ctx.header : "", !_ctx.a ? "d" : ""]) }) +}` + ) +}) diff --git a/packages/uni-mp-compiler/src/transforms/vSlot.ts b/packages/uni-mp-compiler/src/transforms/vSlot.ts index c5534d05bbf..db228450288 100644 --- a/packages/uni-mp-compiler/src/transforms/vSlot.ts +++ b/packages/uni-mp-compiler/src/transforms/vSlot.ts @@ -12,6 +12,7 @@ import { type ComponentNode, type CompoundExpressionNode, type DirectiveNode, + type ElementNode, ElementTypes, ErrorCodes, type ExpressionNode, @@ -50,6 +51,13 @@ import { } from './utils' import { createVForArrowFunctionExpression } from './vFor' import { DYNAMIC_SLOT } from '../runtimeHelpers' +import { processExpression } from './transformExpression' + +type Condition = { + slotName: string | CompoundExpressionNode + condition: string + expression?: ExpressionNode +} export const transformSlot: NodeTransform = (node, context) => { if (!isUserComponent(node, context as any)) { @@ -58,6 +66,7 @@ export const transformSlot: NodeTransform = (node, context) => { const { tag, children } = node const slots = new Set() + const conditions: Condition[] = [] const onComponentSlot = findDir(node, 'slot', true) const implicitDefaultChildren: TemplateChildNode[] = [] const isMiniProgramComponent = context.isMiniProgramComponent(tag) @@ -83,6 +92,12 @@ export const transformSlot: NodeTransform = (node, context) => { if (slotElement.type !== NodeTypes.COMMENT) { implicitDefaultChildren.push(slotElement) } + if ( + slotElement.type === NodeTypes.ELEMENT && + slotElement.tag === 'template' + ) { + buildConditions(slotElement, SLOT_DEFAULT_NAME, conditions) + } continue } @@ -114,6 +129,9 @@ export const transformSlot: NodeTransform = (node, context) => { if (slotName) { slots.add(slotName) + if (slotElement.tag === 'template') { + buildConditions(slotElement, slotName, conditions) + } } } @@ -136,6 +154,7 @@ export const transformSlot: NodeTransform = (node, context) => { transformTemplateSlotElement(onComponentSlot, templateNode, node, context) node.children = [templateNode] } + const slotConditionMap = buildSlotConditionMap(conditions) // 不支持 $slots, 则自动补充 props if (slots.size && !context.miniProgram.slot.$slots) { const slotsArr = [...slots] @@ -145,10 +164,27 @@ export const transformSlot: NodeTransform = (node, context) => { const children: (string | ExpressionNode)[] = [] const len = slotsArr.length - 1 slotsArr.forEach((name, index) => { - if (isString(name)) { - children.push(`'${dynamicSlotName(name)}'`) + if (slotConditionMap.has(name)) { + const slotName = isString(name) + ? createSimpleExpression(dynamicSlotName(name), true) + : name + children.push( + createCompoundExpression([ + processExpression( + createSimpleExpression( + genExpr(slotConditionMap.get(name) as ExpressionNode), + false + ), + context + ), + ' ? ', + slotName, + ' : ', + createSimpleExpression('', true), + ]) + ) } else { - children.push(name) + children.push(isString(name) ? `'${dynamicSlotName(name)}'` : name) } if (index < len) { children.push(',') @@ -161,7 +197,12 @@ export const transformSlot: NodeTransform = (node, context) => { ]) } else { value = `[${slotsArr - .map((name) => `'${dynamicSlotName(name as string)}'`) + .map((name) => { + const slotName = dynamicSlotName(name as string) + return slotConditionMap.has(name) + ? `${genExpr(slotConditionMap.get(name))} ? '${slotName}' : ''` + : `'${slotName}'` + }) .join(',')}]` } node.props.unshift(createBindDirectiveNode(ATTR_VUE_SLOTS, value)) @@ -419,3 +460,72 @@ export function createVSlotCallExpression( ]), ]) } + +function buildConditions( + node: ElementNode, + slotName: string | CompoundExpressionNode, + conditions: Condition[] +) { + const directives = [ + { condition: 'if', hasExpr: true }, + { condition: 'else-if', hasExpr: true }, + { condition: 'else', hasExpr: false }, + ] + for (const { condition, hasExpr } of directives) { + const dir = findDir(node, condition, true) + if (dir && (!hasExpr || dir.exp)) { + conditions.push({ + slotName, + condition, + expression: hasExpr ? dir.exp : undefined, + }) + return + } + } +} + +function buildSlotConditionMap(conditions: Condition[]) { + const slotConditionMap = new Map() + const expressions: ExpressionNode[] = [] + for (const condition of conditions) { + switch (condition.condition) { + case 'if': + // 直接设置为当前条件 + slotConditionMap.set(condition.slotName, condition.expression!) + expressions.push(condition.expression!) + break + case 'else-if': + // else-if 前面的条件都取反,并当前条件 + const expr = createCompoundExpression([ + ...expressions + .map((expr, idx) => [ + idx === 0 ? '' : ' && ', + createCompoundExpression(['!', '(', expr, ')']), + ]) + .flat(), + ' && ', + condition.expression!, + ]) + expressions.push(condition.expression!) + slotConditionMap.set(condition.slotName, expr) + break + case 'else': + // 条件都取反 + slotConditionMap.set( + condition.slotName, + createCompoundExpression( + expressions + .map((expr, idx) => [ + idx === 0 ? '' : ' && ', + createCompoundExpression(['!', '(', expr, ')']), + ]) + .flat() + ) + ) + // 清空存储的 if/else-if 表达式 + expressions.length = 0 + break + } + } + return slotConditionMap +} diff --git a/packages/uni-mp-core/src/runtime/componentProps.ts b/packages/uni-mp-core/src/runtime/componentProps.ts index dd5fc761d44..f6b46f806d5 100644 --- a/packages/uni-mp-core/src/runtime/componentProps.ts +++ b/packages/uni-mp-core/src/runtime/componentProps.ts @@ -48,11 +48,17 @@ function initDefaultProps( const $slots = Object.create(null) newVal && newVal.forEach((slotName: string) => { - $slots[slotName] = true + if (slotName) { + $slots[slotName] = true + } }) this.setData({ $slots, }) + if (this.$vm) { + this.$vm.$.slots = $slots + this.$vm.$forceUpdate() + } } properties.uS = { type: null,