Skip to content

Commit af78bcf

Browse files
defccyyx990803
authored andcommitted
Support v-if multiple conditions (vuejs#4271)
* add if conditions * update v-if conditional * update test * update test case * add test case * update if conditions * update walkThroughConditionsBlocks * update v-elseif * update v-once with v-elseif test case * update style with v-elseif * update flow type
1 parent a0d8603 commit af78bcf

File tree

10 files changed

+332
-31
lines changed

10 files changed

+332
-31
lines changed

flow/compiler.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ declare type ModuleOptions = {
4141
}
4242

4343
declare type ASTModifiers = { [key: string]: boolean }
44+
declare type ASTIfConditions = Array<{ exp: ?string; block: ASTElement }>
4445

4546
declare type ASTElementHandler = {
4647
value: string;
@@ -95,8 +96,9 @@ declare type ASTElement = {
9596

9697
if?: string;
9798
ifProcessed?: boolean;
99+
elseif?: string;
98100
else?: true;
99-
elseBlock?: ASTElement;
101+
conditions?: ASTIfConditions;
100102

101103
for?: string;
102104
forProcessed?: boolean;

src/compiler/codegen/index.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,17 +110,27 @@ function genOnce (el: ASTElement): string {
110110
}
111111
}
112112

113-
// v-if with v-once shuold generate code like (a)?_m(0):_m(1)
114113
function genIf (el: any): string {
115-
const exp = el.if
116114
el.ifProcessed = true // avoid recursion
117-
return `(${exp})?${el.once ? genOnce(el) : genElement(el)}:${genElse(el)}`
115+
return genIfConditions(el.conditions)
118116
}
119117

120-
function genElse (el: ASTElement): string {
121-
return el.elseBlock
122-
? genElement(el.elseBlock)
123-
: '_e()'
118+
function genIfConditions (conditions: ASTIfConditions): string {
119+
if (!conditions.length) {
120+
return '_e()'
121+
}
122+
123+
var condition = conditions.shift()
124+
if (condition.exp) {
125+
return `(${condition.exp})?${genTernaryExp(condition.block)}:${genIfConditions(conditions)}`
126+
} else {
127+
return `${genTernaryExp(condition.block)}`
128+
}
129+
130+
// v-if with v-once shuold generate code like (a)?_m(0):_m(1)
131+
function genTernaryExp (el) {
132+
return el.once ? genOnce(el) : genElement(el)
133+
}
124134
}
125135

126136
function genFor (el: any): string {

src/compiler/optimizer.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,18 @@ function markStaticRoots (node: ASTNode, isInFor: boolean) {
8080
markStaticRoots(node.children[i], isInFor || !!node.for)
8181
}
8282
}
83-
if (node.elseBlock) {
84-
markStaticRoots(node.elseBlock, isInFor)
83+
if (node.conditions) {
84+
walkThroughConditionsBlocks(node.conditions, isInFor)
8585
}
8686
}
8787
}
8888

89+
function walkThroughConditionsBlocks (conditionBlocks: ASTIfConditions, isInFor: boolean): void {
90+
for (let i = 1, len = conditionBlocks.length; i < len; i++) {
91+
markStaticRoots(conditionBlocks[i].block, isInFor)
92+
}
93+
}
94+
8995
function isStatic (node: ASTNode): boolean {
9096
if (node.type === 2) { // expression
9197
return false

src/compiler/parser/index.js

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,13 @@ export function parse (
154154
root = element
155155
checkRootConstraints(root)
156156
} else if (!stack.length) {
157-
// allow 2 root elements with v-if and v-else
158-
if (root.if && element.else) {
157+
// allow root elements with v-if, v-elseif and v-else
158+
if (root.if && (element.elseif || element.else)) {
159159
checkRootConstraints(element)
160-
root.elseBlock = element
160+
addIfCondition(root, {
161+
exp: element.elseif,
162+
block: element
163+
})
161164
} else if (process.env.NODE_ENV !== 'production' && !warned) {
162165
warned = true
163166
warn(
@@ -166,8 +169,8 @@ export function parse (
166169
}
167170
}
168171
if (currentParent && !element.forbidden) {
169-
if (element.else) { // else block
170-
processElse(element, currentParent)
172+
if (element.elseif || element.else) {
173+
processIfConditions(element, currentParent)
171174
} else if (element.slotScope) { // scoped slot
172175
currentParent.plain = false
173176
const name = element.slotTarget || 'default'
@@ -316,23 +319,43 @@ function processIf (el) {
316319
const exp = getAndRemoveAttr(el, 'v-if')
317320
if (exp) {
318321
el.if = exp
319-
}
320-
if (getAndRemoveAttr(el, 'v-else') != null) {
321-
el.else = true
322+
addIfCondition(el, {
323+
exp: exp,
324+
block: el
325+
})
326+
} else {
327+
if (getAndRemoveAttr(el, 'v-else') != null) {
328+
el.else = true
329+
}
330+
const elseif = getAndRemoveAttr(el, 'v-elseif')
331+
if (elseif) {
332+
el.elseif = elseif
333+
}
322334
}
323335
}
324336

325-
function processElse (el, parent) {
337+
function processIfConditions (el, parent) {
326338
const prev = findPrevElement(parent.children)
327339
if (prev && prev.if) {
328-
prev.elseBlock = el
340+
addIfCondition(prev, {
341+
exp: el.elseif,
342+
block: el
343+
})
329344
} else if (process.env.NODE_ENV !== 'production') {
330345
warn(
331-
`v-else used on element <${el.tag}> without corresponding v-if.`
346+
`v-${el.elseif ? ('elseif="' + el.elseif + '"') : 'else'} ` +
347+
`used on element <${el.tag}> without corresponding v-if.`
332348
)
333349
}
334350
}
335351

352+
function addIfCondition (el, condition) {
353+
if (!el.conditions) {
354+
el.conditions = []
355+
}
356+
el.conditions.push(condition)
357+
}
358+
336359
function processOnce (el) {
337360
const once = getAndRemoveAttr(el, 'v-once')
338361
if (once != null) {

test/unit/features/directives/if.spec.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,55 @@ describe('Directive v-if', () => {
8888
}).then(done)
8989
})
9090

91+
it('should work well with v-elseif', done => {
92+
const vm = new Vue({
93+
template: `
94+
<div>
95+
<span v-if="foo">hello</span>
96+
<span v-elseif="bar">elseif</span>
97+
<span v-else>bye</span>
98+
</div>
99+
`,
100+
data: { foo: true, bar: false }
101+
}).$mount()
102+
expect(vm.$el.innerHTML.trim()).toBe('<span>hello</span>')
103+
vm.foo = false
104+
waitForUpdate(() => {
105+
expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span>')
106+
vm.bar = true
107+
}).then(() => {
108+
expect(vm.$el.innerHTML.trim()).toBe('<span>elseif</span>')
109+
vm.bar = false
110+
}).then(() => {
111+
expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span>')
112+
vm.foo = true
113+
}).then(() => {
114+
expect(vm.$el.innerHTML.trim()).toBe('<span>hello</span>')
115+
vm.foo = false
116+
vm.bar = {}
117+
}).then(() => {
118+
expect(vm.$el.innerHTML.trim()).toBe('<span>elseif</span>')
119+
vm.bar = 0
120+
}).then(() => {
121+
expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span>')
122+
vm.bar = []
123+
}).then(() => {
124+
expect(vm.$el.innerHTML.trim()).toBe('<span>elseif</span>')
125+
vm.bar = null
126+
}).then(() => {
127+
expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span>')
128+
vm.bar = '0'
129+
}).then(() => {
130+
expect(vm.$el.innerHTML.trim()).toBe('<span>elseif</span>')
131+
vm.bar = undefined
132+
}).then(() => {
133+
expect(vm.$el.innerHTML.trim()).toBe('<span>bye</span>')
134+
vm.bar = 1
135+
}).then(() => {
136+
expect(vm.$el.innerHTML.trim()).toBe('<span>elseif</span>')
137+
}).then(done)
138+
})
139+
91140
it('should work well with v-for', done => {
92141
const vm = new Vue({
93142
template: `

test/unit/features/directives/once.spec.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,56 @@ describe('Directive v-once', () => {
242242
}).then(done)
243243
})
244244

245+
it('should work inside v-for with nested v-elseif and v-else', done => {
246+
const vm = new Vue({
247+
data: {
248+
tester: false,
249+
list: [{ id: 0, text: 'a', tester: true, truthy: 'y' }]
250+
},
251+
template: `
252+
<div v-if="0"></div>
253+
<div v-elseif="tester">
254+
<div v-for="i in list" :key="i.id">
255+
<span v-if="i.tester" v-once>{{ i.truthy }}</span>
256+
<span v-elseif="tester" v-once>{{ i.text }}elseif</span>
257+
<span v-else v-once>{{ i.text }}</span>
258+
</div>
259+
</div>
260+
<div v-else>
261+
<div v-for="i in list" :key="i.id">
262+
<span v-if="i.tester" v-once>{{ i.truthy }}</span>
263+
<span v-elseif="tester">{{ i.text }}elseif</span>
264+
<span v-else v-once>{{ i.text }}</span>
265+
</div>
266+
</div>
267+
`
268+
}).$mount()
269+
270+
expectTextContent(vm, 'y')
271+
vm.list[0].truthy = 'yy'
272+
waitForUpdate(() => {
273+
expectTextContent(vm, 'y')
274+
vm.list[0].tester = false
275+
}).then(() => {
276+
expectTextContent(vm, 'a')
277+
vm.list[0].text = 'nn'
278+
}).then(() => {
279+
expectTextContent(vm, 'a')
280+
vm.tester = true
281+
}).then(() => {
282+
expectTextContent(vm, 'nnelseif')
283+
vm.list[0].text = 'xx'
284+
}).then(() => {
285+
expectTextContent(vm, 'nnelseif')
286+
vm.list[0].tester = true
287+
}).then(() => {
288+
expectTextContent(vm, 'yy')
289+
vm.list[0].truthy = 'nn'
290+
}).then(() => {
291+
expectTextContent(vm, 'yy')
292+
}).then(done)
293+
})
294+
245295
it('should warn inside non-keyed v-for', () => {
246296
const vm = new Vue({
247297
data: {

test/unit/features/directives/style.spec.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,4 +304,39 @@ describe('Directive v-bind:style', () => {
304304
expect(style.marginTop).toBe('12px')
305305
}).then(done)
306306
})
307+
308+
it('should not merge for v-if, v-elseif and v-else elements', (done) => {
309+
const vm = new Vue({
310+
template:
311+
'<div>' +
312+
'<section style="color: blue" :style="style" v-if="foo"></section>' +
313+
'<section style="margin-top: 12px" v-elseif="bar"></section>' +
314+
'<section style="margin-bottom: 24px" v-else></section>' +
315+
'<div></div>' +
316+
'</div>',
317+
data: {
318+
foo: true,
319+
bar: false,
320+
style: {
321+
fontSize: '12px'
322+
}
323+
}
324+
}).$mount()
325+
const style = vm.$el.children[0].style
326+
expect(style.fontSize).toBe('12px')
327+
expect(style.color).toBe('blue')
328+
waitForUpdate(() => {
329+
vm.foo = false
330+
}).then(() => {
331+
expect(style.color).toBe('')
332+
expect(style.fontSize).toBe('')
333+
expect(style.marginBottom).toBe('24px')
334+
vm.bar = true
335+
}).then(() => {
336+
expect(style.color).toBe('')
337+
expect(style.fontSize).toBe('')
338+
expect(style.marginBottom).toBe('')
339+
expect(style.marginTop).toBe('12px')
340+
}).then(done)
341+
})
307342
})

test/unit/modules/compiler/codegen.spec.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,27 @@ describe('codegen', () => {
8383
)
8484
})
8585

86+
it('generate v-elseif directive', () => {
87+
assertCodegen(
88+
'<div><p v-if="show">hello</p><p v-elseif="hide">world</p></div>',
89+
`with(this){return _h('div',[(show)?_h('p',["hello"]):(hide)?_h('p',["world"]):_e()])}`
90+
)
91+
})
92+
93+
it('generate v-elseif with v-else directive', () => {
94+
assertCodegen(
95+
'<div><p v-if="show">hello</p><p v-elseif="hide">world</p><p v-else>bye</p></div>',
96+
`with(this){return _h('div',[(show)?_h('p',["hello"]):(hide)?_h('p',["world"]):_h('p',["bye"])])}`
97+
)
98+
})
99+
100+
it('generate mutli v-elseif with v-else directive', () => {
101+
assertCodegen(
102+
'<div><p v-if="show">hello</p><p v-elseif="hide">world</p><p v-elseif="3">elseif</p><p v-else>bye</p></div>',
103+
`with(this){return _h('div',[(show)?_h('p',["hello"]):(hide)?_h('p',["world"]):(3)?_h('p',["elseif"]):_h('p',["bye"])])}`
104+
)
105+
})
106+
86107
it('generate ref', () => {
87108
assertCodegen(
88109
'<p ref="component1"></p>',

test/unit/modules/compiler/optimizer.spec.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ describe('optimizer', () => {
6666
optimize(ast, baseOptions)
6767
expect(ast.static).toBe(false)
6868
expect(ast.children[0].static).toBe(false)
69-
expect(ast.children[0].elseBlock.static).toBeUndefined()
69+
expect(ast.children[0].conditions[1].block.static).toBeUndefined()
7070
})
7171

7272
it('v-pre directive', () => {
@@ -213,14 +213,28 @@ describe('optimizer', () => {
213213
it('mark static trees inside v-for with nested v-else and v-once', () => {
214214
const ast = parse(`
215215
<div v-if="1"></div>
216+
<div v-elseif="2">
217+
<div v-for="i in 10" :key="i">
218+
<div v-if="1">{{ i }}</div>
219+
<div v-elseif="2" v-once>{{ i }}</div>
220+
<div v-else v-once>{{ i }}</div>
221+
</div>
222+
</div>
216223
<div v-else>
217224
<div v-for="i in 10" :key="i">
218225
<div v-if="1">{{ i }}</div>
219226
<div v-else v-once>{{ i }}</div>
220227
</div>
221-
<div>`, baseOptions)
228+
</div>
229+
`, baseOptions)
222230
optimize(ast, baseOptions)
223-
expect(ast.elseBlock.children[0].children[0].elseBlock.staticRoot).toBe(false)
224-
expect(ast.elseBlock.children[0].children[0].elseBlock.staticInFor).toBe(true)
231+
expect(ast.conditions[1].block.children[0].children[0].conditions[1].block.staticRoot).toBe(false)
232+
expect(ast.conditions[1].block.children[0].children[0].conditions[1].block.staticInFor).toBe(true)
233+
234+
expect(ast.conditions[1].block.children[0].children[0].conditions[2].block.staticRoot).toBe(false)
235+
expect(ast.conditions[1].block.children[0].children[0].conditions[2].block.staticInFor).toBe(true)
236+
237+
expect(ast.conditions[2].block.children[0].children[0].conditions[1].block.staticRoot).toBe(false)
238+
expect(ast.conditions[2].block.children[0].children[0].conditions[1].block.staticInFor).toBe(true)
225239
})
226240
})

0 commit comments

Comments
 (0)