Просмотр исходного кода

feat(v-for): support iterables in v-for (#8179)

Jeremy Dubois 7 лет назад
Родитель
Сommit
d40eb9c288

+ 16 - 6
src/core/instance/render-helpers/render-list.js

@@ -1,6 +1,6 @@
 /* @flow */
 
-import { isObject, isDef } from 'core/util/index'
+import { isObject, isDef, hasSymbol } from 'core/util/index'
 
 /**
  * Runtime helper for rendering v-for lists.
@@ -25,11 +25,21 @@ export function renderList (
       ret[i] = render(i + 1, i)
     }
   } else if (isObject(val)) {
-    keys = Object.keys(val)
-    ret = new Array(keys.length)
-    for (i = 0, l = keys.length; i < l; i++) {
-      key = keys[i]
-      ret[i] = render(val[key], key, i)
+    if (hasSymbol && val[Symbol.iterator]) {
+      ret = []
+      const iterator: Iterator<any> = val[Symbol.iterator]()
+      let result = iterator.next()
+      while (!result.done) {
+        ret.push(render(result.value, ret.length))
+        result = iterator.next()
+      }
+    } else {
+      keys = Object.keys(val)
+      ret = new Array(keys.length)
+      for (i = 0, l = keys.length; i < l; i++) {
+        key = keys[i]
+        ret[i] = render(val[key], key, i)
+      }
     }
   }
   if (!isDef(ret)) {

+ 200 - 0
test/unit/features/directives/for.spec.js

@@ -1,4 +1,5 @@
 import Vue from 'vue'
+import { hasSymbol } from 'core/util/env'
 
 describe('Directive v-for', () => {
   it('should render array of primitive values', done => {
@@ -123,6 +124,205 @@ describe('Directive v-for', () => {
     }).then(done)
   })
 
+  if (hasSymbol) {
+    it('should render iterable of primitive values', done => {
+      const iterable = {
+        models: ['a', 'b', 'c'],
+        index: 0,
+        [Symbol.iterator] () {
+          const iterator = {
+            index: 0,
+            models: this.models,
+            next () {
+              if (this.index < this.models.length) {
+                return { value: this.models[this.index++] }
+              } else {
+                return { done: true }
+              }
+            }
+          }
+          return iterator
+        }
+      }
+      const vm = new Vue({
+        template: `
+          <div>
+            <span v-for="item in list">{{item}}</span>
+          </div>
+        `,
+        data: {
+          list: iterable
+        }
+      }).$mount()
+      expect(vm.$el.innerHTML).toBe('<span>a</span><span>b</span><span>c</span>')
+      Vue.set(vm.list.models, 0, 'd')
+      waitForUpdate(() => {
+        expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span>')
+        vm.list.models.push('d')
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span><span>d</span>')
+        vm.list.models.splice(1, 2)
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe('<span>d</span><span>d</span>')
+        vm.list.models = ['x', 'y']
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe('<span>x</span><span>y</span>')
+      }).then(done)
+    })
+
+    it('should render iterable of primitive values with index', done => {
+      const iterable = {
+        models: ['a', 'b', 'c'],
+        index: 0,
+        [Symbol.iterator] () {
+          const iterator = {
+            index: 0,
+            models: this.models,
+            next () {
+              if (this.index < this.models.length) {
+                return { value: this.models[this.index++] }
+              } else {
+                return { done: true }
+              }
+            }
+          }
+          return iterator
+        }
+      }
+
+      const vm = new Vue({
+        template: `
+          <div>
+            <span v-for="(item, i) in list">{{i}}-{{item}}</span>
+          </div>
+        `,
+        data: {
+          list: iterable
+        }
+      }).$mount()
+      expect(vm.$el.innerHTML).toBe('<span>0-a</span><span>1-b</span><span>2-c</span>')
+      Vue.set(vm.list.models, 0, 'd')
+      waitForUpdate(() => {
+        expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-b</span><span>2-c</span>')
+        vm.list.models.push('d')
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-b</span><span>2-c</span><span>3-d</span>')
+        vm.list.models.splice(1, 2)
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-d</span>')
+        vm.list.models = ['x', 'y']
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe('<span>0-x</span><span>1-y</span>')
+      }).then(done)
+    })
+
+    it('should render iterable of object values', done => {
+      const iterable = {
+        models: [
+          { value: 'a' },
+          { value: 'b' },
+          { value: 'c' }
+        ],
+        index: 0,
+        [Symbol.iterator] () {
+          const iterator = {
+            index: 0,
+            models: this.models,
+            next () {
+              if (this.index < this.models.length) {
+                return { value: this.models[this.index++] }
+              } else {
+                return { done: true }
+              }
+            }
+          }
+          return iterator
+        }
+      }
+
+      const vm = new Vue({
+        template: `
+          <div>
+            <span v-for="item in list">{{item.value}}</span>
+          </div>
+        `,
+        data: {
+          list: iterable
+        }
+      }).$mount()
+      expect(vm.$el.innerHTML).toBe('<span>a</span><span>b</span><span>c</span>')
+      Vue.set(vm.list.models, 0, { value: 'd' })
+      waitForUpdate(() => {
+        expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span>')
+        vm.list.models[0].value = 'e'
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe('<span>e</span><span>b</span><span>c</span>')
+        vm.list.models.push({})
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe('<span>e</span><span>b</span><span>c</span><span></span>')
+        vm.list.models.splice(1, 2)
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe('<span>e</span><span></span>')
+        vm.list.models = [{ value: 'x' }, { value: 'y' }]
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe('<span>x</span><span>y</span>')
+      }).then(done)
+    })
+
+    it('should render iterable of object values with index', done => {
+      const iterable = {
+        models: [
+          { value: 'a' },
+          { value: 'b' },
+          { value: 'c' }
+        ],
+        index: 0,
+        [Symbol.iterator] () {
+          const iterator = {
+            index: 0,
+            models: this.models,
+            next () {
+              if (this.index < this.models.length) {
+                return { value: this.models[this.index++] }
+              } else {
+                return { done: true }
+              }
+            }
+          }
+          return iterator
+        }
+      }
+
+      const vm = new Vue({
+        template: `
+          <div>
+            <span v-for="(item, i) in list">{{i}}-{{item.value}}</span>
+          </div>
+        `,
+        data: {
+          list: iterable
+        }
+      }).$mount()
+      expect(vm.$el.innerHTML).toBe('<span>0-a</span><span>1-b</span><span>2-c</span>')
+      Vue.set(vm.list.models, 0, { value: 'd' })
+      waitForUpdate(() => {
+        expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-b</span><span>2-c</span>')
+        vm.list.models[0].value = 'e'
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-b</span><span>2-c</span>')
+        vm.list.models.push({})
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-b</span><span>2-c</span><span>3-</span>')
+        vm.list.models.splice(1, 2)
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-</span>')
+        vm.list.models = [{ value: 'x' }, { value: 'y' }]
+      }).then(() => {
+        expect(vm.$el.innerHTML).toBe('<span>0-x</span><span>1-y</span>')
+      }).then(done)
+    })
+  }
+
   it('should render an Object', done => {
     const vm = new Vue({
       template: `