Skip to content

fix(mp): 修复 v-if 和 v-slot 同时使用时 v-if 不起作用的 Bug #5622

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/uni-mp-alipay/__tests__/vSlot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ describe('mp-alipay: transform v-slot', () => {
test('v-if + scoped slots', () => {
assert(
`<custom><template v-if="ok" v-slot:default="slotProps"><view>{{ slotProps.item }}</view></template></custom>`,
`<custom u-s="{{['d']}}" u-i="2a9ec0b0-0" onVI="__l"><block a:if="{{a}}"><view a:for="{{b}}" a:for-item="slotProps" a:key="b" slot="{{slotProps.c}}"><view>{{slotProps.a}}</view></view></block></custom>`,
`<custom u-s="{{c}}" u-i="2a9ec0b0-0" onVI="__l"><block a:if="{{a}}"><view a:for="{{b}}" a:for-item="slotProps" a:key="b" slot="{{slotProps.c}}"><view>{{slotProps.a}}</view></view></block></custom>`,
`(_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' : ''] })
}`
)
})
Expand Down
4 changes: 2 additions & 2 deletions packages/uni-mp-baidu/__tests__/vSlot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ describe('compiler: transform v-slot', () => {
test('v-if + scoped slots', () => {
assert(
`<custom><template v-if="ok" v-slot:default="slotProps"><view>{{ slotProps.item }}</view></template></custom>`,
`<custom u-s="{{['d']}}" u-i="2a9ec0b0-0"><view s-if="{{a}}"><block s-for="slotProps in b trackBy slotProps.a"><view>{{slotProps.b}}</view></block></view></custom>`,
`<custom u-s="{{c}}" u-i="2a9ec0b0-0"><view s-if="{{a}}"><block s-for="slotProps in b trackBy slotProps.a"><view>{{slotProps.b}}</view></block></view></custom>`,
`(_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' : ''] })
}`
)
})
Expand Down
122 changes: 118 additions & 4 deletions packages/uni-mp-compiler/__tests__/vSlot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ describe('compiler: transform v-slot', () => {
)
assert(
`<uni-list-item class="item"><template v-slot:body v-if="ok"><view class="item"></view></template></uni-list-item>`,
`<uni-list-item u-s="{{['body']}}" class="item" u-i="2a9ec0b0-0"><view wx:if="{{a}}" class="item" slot="body"></view></uni-list-item>`,
`<uni-list-item u-s="{{b}}" class="item" u-i="2a9ec0b0-0"><view wx:if="{{a}}" class="item" slot="body"></view></uni-list-item>`,
`(_ctx, _cache) => {
return _e({ a: _ctx.ok }, _ctx.ok ? {} : {})
return _e({ a: _ctx.ok }, _ctx.ok ? {} : {}, { b: [_ctx.ok ? 'body' : ''] })
}`
)
})
Expand Down Expand Up @@ -88,9 +88,9 @@ describe('compiler: transform v-slot', () => {
test('v-if + scoped slots', () => {
assert(
`<custom><template v-if="ok" v-slot:default="slotProps"><view>{{ slotProps.item }}</view></template></custom>`,
`<custom u-s="{{['d']}}" u-i="2a9ec0b0-0"><block wx:if="{{a}}"><view wx:for="{{b}}" wx:for-item="slotProps" wx:key="b" slot="{{slotProps.c}}"><view>{{slotProps.a}}</view></view></block></custom>`,
`<custom u-s="{{c}}" u-i="2a9ec0b0-0"><block wx:if="{{a}}"><view wx:for="{{b}}" wx:for-item="slotProps" wx:key="b" slot="{{slotProps.c}}"><view>{{slotProps.a}}</view></view></block></custom>`,
`(_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' : ''] })
}`
)
})
Expand Down Expand Up @@ -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(
`<custom><template v-if="a" #header>hello</template></custom>`,
`<custom u-s="{{b}}" u-i="2a9ec0b0-0"><view wx:if="{{a}}" slot="header">hello</view></custom>`,
`(_ctx, _cache) => {
return _e({ a: _ctx.a }, _ctx.a ? {} : {}, { b: [_ctx.a ? 'header' : ''] })
}`
)

assert(
`<custom><template v-if="a">hello</template></custom>`,
`<custom u-s="{{b}}" u-i="2a9ec0b0-0"><block wx:if="{{a}}">hello</block></custom>`,
`(_ctx, _cache) => {
return _e({ a: _ctx.a }, _ctx.a ? {} : {}, { b: [_ctx.a ? 'd' : ''] })
}`
)

assert(
`<custom><template v-if="a">hello</template><template v-else #footer>hello</template></custom>`,
`<custom u-s="{{b}}" u-i="2a9ec0b0-0"><block wx:if="{{a}}">hello</block><view wx:else slot="footer">hello</view></custom>`,
`(_ctx, _cache) => {
return _e({ a: _ctx.a }, _ctx.a ? {} : {}, { b: [!_ctx.a ? 'footer' : '', _ctx.a ? 'd' : ''] })
}`
)

assert(
`<custom><template v-if="a" #header>hello</template><template v-if="b" #footer>hello</template></custom>`,
`<custom u-s="{{c}}" u-i="2a9ec0b0-0"><view wx:if="{{a}}" slot="header">hello</view><view wx:if="{{b}}" slot="footer">hello</view></custom>`,
`(_ctx, _cache) => {
return _e({ a: _ctx.a }, _ctx.a ? {} : {}, { b: _ctx.b }, _ctx.b ? {} : {}, { c: [_ctx.a ? 'header' : '', _ctx.b ? 'footer' : ''] })
}`
)

assert(
`<custom><template v-if="a" #header>hello</template><template v-if="b">hello</template></custom>`,
`<custom u-s="{{c}}" u-i="2a9ec0b0-0"><view wx:if="{{a}}" slot="header">hello</view><block wx:if="{{b}}">hello</block></custom>`,
`(_ctx, _cache) => {
return _e({ a: _ctx.a }, _ctx.a ? {} : {}, { b: _ctx.b }, _ctx.b ? {} : {}, { c: [_ctx.a ? 'header' : '', _ctx.b ? 'd' : ''] })
}`
)

assert(
`<custom><template v-if="a" #header>hello</template><template v-else #footer>hello</template></custom>`,
`<custom u-s="{{b}}" u-i="2a9ec0b0-0"><view wx:if="{{a}}" slot="header">hello</view><view wx:else slot="footer">hello</view></custom>`,
`(_ctx, _cache) => {
return _e({ a: _ctx.a }, _ctx.a ? {} : {}, { b: [_ctx.a ? 'header' : '', !_ctx.a ? 'footer' : ''] })
}`
)

assert(
`<custom><template v-if="a" #header>hello</template><template v-else>hello</template></custom>`,
`<custom u-s="{{b}}" u-i="2a9ec0b0-0"><view wx:if="{{a}}" slot="header">hello</view><block wx:else>hello</block></custom>`,
`(_ctx, _cache) => {
return _e({ a: _ctx.a }, _ctx.a ? {} : {}, { b: [_ctx.a ? 'header' : '', !_ctx.a ? 'd' : ''] })
}`
)

assert(
`<custom><template v-if="a" #header>hello</template><template v-else-if="b" #footer>hello</template></custom>`,
`<custom u-s="{{c}}" u-i="2a9ec0b0-0"><view wx:if="{{a}}" slot="header">hello</view><view wx:elif="{{b}}" slot="footer">hello</view></custom>`,
`(_ctx, _cache) => {
return _e({ a: _ctx.a }, _ctx.a ? {} : _ctx.b ? {} : {}, { b: _ctx.b, c: [_ctx.a ? 'header' : '', !_ctx.a && _ctx.b ? 'footer' : ''] })
}`
)

assert(
`<custom><template v-if="a" #header>hello</template><template v-else-if="b" #footer>hello</template><template v-else-if="c" #header2>hello</template></custom>`,
`<custom u-s="{{d}}" u-i="2a9ec0b0-0"><view wx:if="{{a}}" slot="header">hello</view><view wx:elif="{{b}}" slot="footer">hello</view><view wx:elif="{{c}}" slot="header2">hello</view></custom>`,
`(_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(
`<custom><template v-if="a" #header>hello</template><template v-else-if="b" #footer>hello</template><template v-if="c" #header2>hello</template></custom>`,
`<custom u-s="{{d}}" u-i="2a9ec0b0-0"><view wx:if="{{a}}" slot="header">hello</view><view wx:elif="{{b}}" slot="footer">hello</view><view wx:if="{{c}}" slot="header2">hello</view></custom>`,
`(_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(
`<custom><template v-if="a" #header>hello</template><template v-else-if="b" #footer>hello</template><template v-else #footer2>hello</template></custom>`,
`<custom u-s="{{c}}" u-i="2a9ec0b0-0"><view wx:if="{{a}}" slot="header">hello</view><view wx:elif="{{b}}" slot="footer">hello</view><view wx:else slot="footer2">hello</view></custom>`,
`(_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(
`<custom><template v-if="a" #header>hello</template><template v-else-if="b" #footer>hello</template><template v-else-if="c" #footer3>hello</template><template v-else #footer2>hello</template></custom>`,
`<custom u-s="{{d}}" u-i="2a9ec0b0-0"><view wx:if="{{a}}" slot="header">hello</view><view wx:elif="{{b}}" slot="footer">hello</view><view wx:elif="{{c}}" slot="footer3">hello</view><view wx:else slot="footer2">hello</view></custom>`,
`(_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(
`<custom><template v-if="a" #[header]>hello</template></custom>`,
`<custom u-s="{{c}}" u-i="2a9ec0b0-0"><view wx:if="{{a}}" slot="{{b}}">hello</view></custom>`,
`(_ctx, _cache) => {
return _e({ a: _ctx.a }, _ctx.a ? { b: _d(_ctx.header) } : {}, { c: _d([_ctx.a ? _ctx.header : ""]) })
}`
)

assert(
`<custom><template v-if="a" #[header]>hello</template><template v-else>hello</template></custom>`,
`<custom u-s="{{c}}" u-i="2a9ec0b0-0"><view wx:if="{{a}}" slot="{{b}}">hello</view><block wx:else>hello</block></custom>`,
`(_ctx, _cache) => {
return _e({ a: _ctx.a }, _ctx.a ? { b: _d(_ctx.header) } : {}, { c: _d([_ctx.a ? _ctx.header : "", !_ctx.a ? "d" : ""]) })
}`
)
})
118 changes: 114 additions & 4 deletions packages/uni-mp-compiler/src/transforms/vSlot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
type ComponentNode,
type CompoundExpressionNode,
type DirectiveNode,
type ElementNode,
ElementTypes,
ErrorCodes,
type ExpressionNode,
Expand Down Expand Up @@ -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)) {
Expand All @@ -58,6 +66,7 @@ export const transformSlot: NodeTransform = (node, context) => {

const { tag, children } = node
const slots = new Set<string | ExpressionNode>()
const conditions: Condition[] = []
const onComponentSlot = findDir(node, 'slot', true)
const implicitDefaultChildren: TemplateChildNode[] = []
const isMiniProgramComponent = context.isMiniProgramComponent(tag)
Expand All @@ -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
}

Expand Down Expand Up @@ -114,6 +129,9 @@ export const transformSlot: NodeTransform = (node, context) => {

if (slotName) {
slots.add(slotName)
if (slotElement.tag === 'template') {
buildConditions(slotElement, slotName, conditions)
}
}
}

Expand All @@ -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]
Expand All @@ -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(',')
Expand All @@ -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))
Expand Down Expand Up @@ -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
}
8 changes: 7 additions & 1 deletion packages/uni-mp-core/src/runtime/componentProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading