Skip to content

Commit d40eb9c

Browse files
mymyouxyyx990803
authored andcommitted
feat(v-for): support iterables in v-for (#8179)
1 parent e1abedb commit d40eb9c

File tree

2 files changed

+216
-6
lines changed

2 files changed

+216
-6
lines changed

src/core/instance/render-helpers/render-list.js

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* @flow */
22

3-
import { isObject, isDef } from 'core/util/index'
3+
import { isObject, isDef, hasSymbol } from 'core/util/index'
44

55
/**
66
* Runtime helper for rendering v-for lists.
@@ -25,11 +25,21 @@ export function renderList (
2525
ret[i] = render(i + 1, i)
2626
}
2727
} else if (isObject(val)) {
28-
keys = Object.keys(val)
29-
ret = new Array(keys.length)
30-
for (i = 0, l = keys.length; i < l; i++) {
31-
key = keys[i]
32-
ret[i] = render(val[key], key, i)
28+
if (hasSymbol && val[Symbol.iterator]) {
29+
ret = []
30+
const iterator: Iterator<any> = val[Symbol.iterator]()
31+
let result = iterator.next()
32+
while (!result.done) {
33+
ret.push(render(result.value, ret.length))
34+
result = iterator.next()
35+
}
36+
} else {
37+
keys = Object.keys(val)
38+
ret = new Array(keys.length)
39+
for (i = 0, l = keys.length; i < l; i++) {
40+
key = keys[i]
41+
ret[i] = render(val[key], key, i)
42+
}
3343
}
3444
}
3545
if (!isDef(ret)) {

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

+200
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Vue from 'vue'
2+
import { hasSymbol } from 'core/util/env'
23

34
describe('Directive v-for', () => {
45
it('should render array of primitive values', done => {
@@ -123,6 +124,205 @@ describe('Directive v-for', () => {
123124
}).then(done)
124125
})
125126

127+
if (hasSymbol) {
128+
it('should render iterable of primitive values', done => {
129+
const iterable = {
130+
models: ['a', 'b', 'c'],
131+
index: 0,
132+
[Symbol.iterator] () {
133+
const iterator = {
134+
index: 0,
135+
models: this.models,
136+
next () {
137+
if (this.index < this.models.length) {
138+
return { value: this.models[this.index++] }
139+
} else {
140+
return { done: true }
141+
}
142+
}
143+
}
144+
return iterator
145+
}
146+
}
147+
const vm = new Vue({
148+
template: `
149+
<div>
150+
<span v-for="item in list">{{item}}</span>
151+
</div>
152+
`,
153+
data: {
154+
list: iterable
155+
}
156+
}).$mount()
157+
expect(vm.$el.innerHTML).toBe('<span>a</span><span>b</span><span>c</span>')
158+
Vue.set(vm.list.models, 0, 'd')
159+
waitForUpdate(() => {
160+
expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span>')
161+
vm.list.models.push('d')
162+
}).then(() => {
163+
expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span><span>d</span>')
164+
vm.list.models.splice(1, 2)
165+
}).then(() => {
166+
expect(vm.$el.innerHTML).toBe('<span>d</span><span>d</span>')
167+
vm.list.models = ['x', 'y']
168+
}).then(() => {
169+
expect(vm.$el.innerHTML).toBe('<span>x</span><span>y</span>')
170+
}).then(done)
171+
})
172+
173+
it('should render iterable of primitive values with index', done => {
174+
const iterable = {
175+
models: ['a', 'b', 'c'],
176+
index: 0,
177+
[Symbol.iterator] () {
178+
const iterator = {
179+
index: 0,
180+
models: this.models,
181+
next () {
182+
if (this.index < this.models.length) {
183+
return { value: this.models[this.index++] }
184+
} else {
185+
return { done: true }
186+
}
187+
}
188+
}
189+
return iterator
190+
}
191+
}
192+
193+
const vm = new Vue({
194+
template: `
195+
<div>
196+
<span v-for="(item, i) in list">{{i}}-{{item}}</span>
197+
</div>
198+
`,
199+
data: {
200+
list: iterable
201+
}
202+
}).$mount()
203+
expect(vm.$el.innerHTML).toBe('<span>0-a</span><span>1-b</span><span>2-c</span>')
204+
Vue.set(vm.list.models, 0, 'd')
205+
waitForUpdate(() => {
206+
expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-b</span><span>2-c</span>')
207+
vm.list.models.push('d')
208+
}).then(() => {
209+
expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-b</span><span>2-c</span><span>3-d</span>')
210+
vm.list.models.splice(1, 2)
211+
}).then(() => {
212+
expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-d</span>')
213+
vm.list.models = ['x', 'y']
214+
}).then(() => {
215+
expect(vm.$el.innerHTML).toBe('<span>0-x</span><span>1-y</span>')
216+
}).then(done)
217+
})
218+
219+
it('should render iterable of object values', done => {
220+
const iterable = {
221+
models: [
222+
{ value: 'a' },
223+
{ value: 'b' },
224+
{ value: 'c' }
225+
],
226+
index: 0,
227+
[Symbol.iterator] () {
228+
const iterator = {
229+
index: 0,
230+
models: this.models,
231+
next () {
232+
if (this.index < this.models.length) {
233+
return { value: this.models[this.index++] }
234+
} else {
235+
return { done: true }
236+
}
237+
}
238+
}
239+
return iterator
240+
}
241+
}
242+
243+
const vm = new Vue({
244+
template: `
245+
<div>
246+
<span v-for="item in list">{{item.value}}</span>
247+
</div>
248+
`,
249+
data: {
250+
list: iterable
251+
}
252+
}).$mount()
253+
expect(vm.$el.innerHTML).toBe('<span>a</span><span>b</span><span>c</span>')
254+
Vue.set(vm.list.models, 0, { value: 'd' })
255+
waitForUpdate(() => {
256+
expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span>')
257+
vm.list.models[0].value = 'e'
258+
}).then(() => {
259+
expect(vm.$el.innerHTML).toBe('<span>e</span><span>b</span><span>c</span>')
260+
vm.list.models.push({})
261+
}).then(() => {
262+
expect(vm.$el.innerHTML).toBe('<span>e</span><span>b</span><span>c</span><span></span>')
263+
vm.list.models.splice(1, 2)
264+
}).then(() => {
265+
expect(vm.$el.innerHTML).toBe('<span>e</span><span></span>')
266+
vm.list.models = [{ value: 'x' }, { value: 'y' }]
267+
}).then(() => {
268+
expect(vm.$el.innerHTML).toBe('<span>x</span><span>y</span>')
269+
}).then(done)
270+
})
271+
272+
it('should render iterable of object values with index', done => {
273+
const iterable = {
274+
models: [
275+
{ value: 'a' },
276+
{ value: 'b' },
277+
{ value: 'c' }
278+
],
279+
index: 0,
280+
[Symbol.iterator] () {
281+
const iterator = {
282+
index: 0,
283+
models: this.models,
284+
next () {
285+
if (this.index < this.models.length) {
286+
return { value: this.models[this.index++] }
287+
} else {
288+
return { done: true }
289+
}
290+
}
291+
}
292+
return iterator
293+
}
294+
}
295+
296+
const vm = new Vue({
297+
template: `
298+
<div>
299+
<span v-for="(item, i) in list">{{i}}-{{item.value}}</span>
300+
</div>
301+
`,
302+
data: {
303+
list: iterable
304+
}
305+
}).$mount()
306+
expect(vm.$el.innerHTML).toBe('<span>0-a</span><span>1-b</span><span>2-c</span>')
307+
Vue.set(vm.list.models, 0, { value: 'd' })
308+
waitForUpdate(() => {
309+
expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-b</span><span>2-c</span>')
310+
vm.list.models[0].value = 'e'
311+
}).then(() => {
312+
expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-b</span><span>2-c</span>')
313+
vm.list.models.push({})
314+
}).then(() => {
315+
expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-b</span><span>2-c</span><span>3-</span>')
316+
vm.list.models.splice(1, 2)
317+
}).then(() => {
318+
expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-</span>')
319+
vm.list.models = [{ value: 'x' }, { value: 'y' }]
320+
}).then(() => {
321+
expect(vm.$el.innerHTML).toBe('<span>0-x</span><span>1-y</span>')
322+
}).then(done)
323+
})
324+
}
325+
126326
it('should render an Object', done => {
127327
const vm = new Vue({
128328
template: `

0 commit comments

Comments
 (0)