Browse Source

implement new v-for iterator syntax (ref: #3073)

Evan You 10 years ago
parent
commit
cec833a9e8

+ 2 - 2
examples/svg/index.html

@@ -14,9 +14,9 @@
         <polygon :points="points"></polygon>
         <circle cx="100" cy="100" r="80"></circle>
         <axis-label
-          v-for="stat in stats"
+          v-for="(stat, index) in stats"
           :stat="stat"
-          :index="$index"
+          :index="index"
           :total="stats.length">
         </axis-label>
       </g>

+ 2 - 1
flow/compiler.js

@@ -94,7 +94,8 @@ declare type ASTElement = {
   for?: string | null,
   key?: string,
   alias?: string,
-  iterator?: string,
+  iterator1?: string,
+  iterator2?: string,
 
   staticClass?: string,
   classBinding?: string,

+ 3 - 2
src/compiler/codegen.js

@@ -101,10 +101,11 @@ function genElse (el: ASTElement): string {
 function genFor (el: ASTElement): string {
   const exp = el.for
   const alias = el.alias
-  const iterator = el.iterator
+  const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
+  const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
   el.for = null // avoid recursion
   return `(${exp})&&_l((${exp}),` +
-    `function(${alias},$index,${iterator || '$key'}){` +
+    `function(${alias}${iterator1}${iterator2}){` +
       `return ${genElement(el)}` +
     '})'
 }

+ 6 - 3
src/compiler/parser/index.js

@@ -22,7 +22,7 @@ const onRE = /^@|^v-on:/
 const argRE = /:(.*)$/
 const modifierRE = /\.[^\.]+/g
 const forAliasRE = /(.*)\s+(?:in|of)\s+(.*)/
-const forIteratorRE = /\((.*),(.*)\)/
+const forIteratorRE = /\(([^,]*),([^,]*)(?:,([^,]*))?\)/
 const camelRE = /[a-z\d][A-Z]/
 
 const decodeHTMLCached = cached(decodeHTML)
@@ -266,8 +266,11 @@ function processFor (el) {
     const alias = inMatch[1].trim()
     const iteratorMatch = alias.match(forIteratorRE)
     if (iteratorMatch) {
-      el.iterator = iteratorMatch[1].trim()
-      el.alias = iteratorMatch[2].trim()
+      el.alias = iteratorMatch[1].trim()
+      el.iterator1 = iteratorMatch[2].trim()
+      if (iteratorMatch[3]) {
+        el.iterator2 = iteratorMatch[3].trim()
+      }
     } else {
       el.alias = alias
     }

+ 3 - 3
src/core/instance/render.js

@@ -112,19 +112,19 @@ export function renderMixin (Vue: Class<Component>) {
     if (Array.isArray(val)) {
       ret = new Array(val.length)
       for (i = 0, l = val.length; i < l; i++) {
-        ret[i] = render(val[i], i, i)
+        ret[i] = render(val[i], i)
       }
     } else if (typeof val === 'number') {
       ret = new Array(val)
       for (i = 0; i < val; i++) {
-        ret[i] = render(i + 1, i, i)
+        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], i, key)
+        ret[i] = render(val[key], key, i)
       }
     }
     return ret

+ 115 - 109
test/unit/features/directives/for.spec.js

@@ -5,7 +5,34 @@ describe('Directive v-for', () => {
     const vm = new Vue({
       template: `
         <div>
-          <span v-for="item in list">{{$index}}-{{item}}</span>
+          <span v-for="item in list">{{item}}</span>
+        </div>
+      `,
+      data: {
+        list: ['a', 'b', 'c']
+      }
+    }).$mount()
+    expect(vm.$el.innerHTML).toBe('<span>a</span><span>b</span><span>c</span>')
+    Vue.set(vm.list, 0, 'd')
+    waitForUpdate(() => {
+      expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span>')
+      vm.list.push('d')
+    }).then(() => {
+      expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span><span>d</span>')
+      vm.list.splice(1, 2)
+    }).then(() => {
+      expect(vm.$el.innerHTML).toBe('<span>d</span><span>d</span>')
+      vm.list = ['x', 'y']
+    }).then(() => {
+      expect(vm.$el.innerHTML).toBe('<span>x</span><span>y</span>')
+    }).then(done)
+  })
+
+  it('should render array of primitive values with index', done => {
+    const vm = new Vue({
+      template: `
+        <div>
+          <span v-for="(item, i) in list">{{i}}-{{item}}</span>
         </div>
       `,
       data: {
@@ -32,7 +59,41 @@ describe('Directive v-for', () => {
     const vm = new Vue({
       template: `
         <div>
-          <span v-for="item in list">{{$index}}-{{item.value}}</span>
+          <span v-for="item in list">{{item.value}}</span>
+        </div>
+      `,
+      data: {
+        list: [
+          { value: 'a' },
+          { value: 'b' },
+          { value: 'c' }
+        ]
+      }
+    }).$mount()
+    expect(vm.$el.innerHTML).toBe('<span>a</span><span>b</span><span>c</span>')
+    Vue.set(vm.list, 0, { value: 'd' })
+    waitForUpdate(() => {
+      expect(vm.$el.innerHTML).toBe('<span>d</span><span>b</span><span>c</span>')
+      vm.list[0].value = 'e'
+    }).then(() => {
+      expect(vm.$el.innerHTML).toBe('<span>e</span><span>b</span><span>c</span>')
+      vm.list.push({})
+    }).then(() => {
+      expect(vm.$el.innerHTML).toBe('<span>e</span><span>b</span><span>c</span><span></span>')
+      vm.list.splice(1, 2)
+    }).then(() => {
+      expect(vm.$el.innerHTML).toBe('<span>e</span><span></span>')
+      vm.list = [{ value: 'x' }, { value: 'y' }]
+    }).then(() => {
+      expect(vm.$el.innerHTML).toBe('<span>x</span><span>y</span>')
+    }).then(done)
+  })
+
+  it('should render array of object values with index', done => {
+    const vm = new Vue({
+      template: `
+        <div>
+          <span v-for="(item, i) in list">{{i}}-{{item.value}}</span>
         </div>
       `,
       data: {
@@ -66,7 +127,31 @@ describe('Directive v-for', () => {
     const vm = new Vue({
       template: `
         <div>
-          <span v-for="val in obj">{{val}}-{{$key}}</span>
+          <span v-for="val in obj">{{val}}</span>
+        </div>
+      `,
+      data: {
+        obj: { a: 0, b: 1, c: 2 }
+      }
+    }).$mount()
+    expect(vm.$el.innerHTML).toBe('<span>0</span><span>1</span><span>2</span>')
+    vm.obj.a = 3
+    waitForUpdate(() => {
+      expect(vm.$el.innerHTML).toBe('<span>3</span><span>1</span><span>2</span>')
+      Vue.set(vm.obj, 'd', 4)
+    }).then(() => {
+      expect(vm.$el.innerHTML).toBe('<span>3</span><span>1</span><span>2</span><span>4</span>')
+      Vue.delete(vm.obj, 'a')
+    }).then(() => {
+      expect(vm.$el.innerHTML).toBe('<span>1</span><span>2</span><span>4</span>')
+    }).then(done)
+  })
+
+  it('should render an Object with key', done => {
+    const vm = new Vue({
+      template: `
+        <div>
+          <span v-for="(val, key) in obj">{{val}}-{{key}}</span>
         </div>
       `,
       data: {
@@ -86,11 +171,35 @@ describe('Directive v-for', () => {
     }).then(done)
   })
 
+  it('should render an Object with key and index', done => {
+    const vm = new Vue({
+      template: `
+        <div>
+          <span v-for="(val, key, i) in obj">{{val}}-{{key}}-{{i}}</span>
+        </div>
+      `,
+      data: {
+        obj: { a: 0, b: 1, c: 2 }
+      }
+    }).$mount()
+    expect(vm.$el.innerHTML).toBe('<span>0-a-0</span><span>1-b-1</span><span>2-c-2</span>')
+    vm.obj.a = 3
+    waitForUpdate(() => {
+      expect(vm.$el.innerHTML).toBe('<span>3-a-0</span><span>1-b-1</span><span>2-c-2</span>')
+      Vue.set(vm.obj, 'd', 4)
+    }).then(() => {
+      expect(vm.$el.innerHTML).toBe('<span>3-a-0</span><span>1-b-1</span><span>2-c-2</span><span>4-d-3</span>')
+      Vue.delete(vm.obj, 'a')
+    }).then(() => {
+      expect(vm.$el.innerHTML).toBe('<span>1-b-0</span><span>2-c-1</span><span>4-d-2</span>')
+    }).then(done)
+  })
+
   it('should render each key of data', done => {
     const vm = new Vue({
       template: `
         <div>
-          <span v-for="val in $data">{{val}}-{{$key}}</span>
+          <span v-for="(val, key) in $data">{{val}}-{{key}}</span>
         </div>
       `,
       data: { a: 0, b: 1, c: 2 }
@@ -102,109 +211,6 @@ describe('Directive v-for', () => {
     }).then(done)
   })
 
-  describe('alternative syntax', () => {
-    it('should render array of primitive values', done => {
-      const vm = new Vue({
-        template: `
-          <div>
-            <span v-for="(i, item) in list">{{i}}-{{item}}</span>
-          </div>
-        `,
-        data: {
-          list: ['a', 'b', 'c']
-        }
-      }).$mount()
-      expect(vm.$el.innerHTML).toBe('<span>0-a</span><span>1-b</span><span>2-c</span>')
-      Vue.set(vm.list, 0, 'd')
-      waitForUpdate(() => {
-        expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-b</span><span>2-c</span>')
-        vm.list.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.splice(1, 2)
-      }).then(() => {
-        expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-d</span>')
-        vm.list = ['x', 'y']
-      }).then(() => {
-        expect(vm.$el.innerHTML).toBe('<span>0-x</span><span>1-y</span>')
-      }).then(done)
-    })
-
-    it('should render array of object values', done => {
-      const vm = new Vue({
-        template: `
-          <div>
-            <span v-for="(i, item) in list">{{i}}-{{item.value}}</span>
-          </div>
-        `,
-        data: {
-          list: [
-            { value: 'a' },
-            { value: 'b' },
-            { value: 'c' }
-          ]
-        }
-      }).$mount()
-      expect(vm.$el.innerHTML).toBe('<span>0-a</span><span>1-b</span><span>2-c</span>')
-      Vue.set(vm.list, 0, { value: 'd' })
-      waitForUpdate(() => {
-        expect(vm.$el.innerHTML).toBe('<span>0-d</span><span>1-b</span><span>2-c</span>')
-        vm.list[0].value = 'e'
-      }).then(() => {
-        expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-b</span><span>2-c</span>')
-        vm.list.push({})
-      }).then(() => {
-        expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-b</span><span>2-c</span><span>3-</span>')
-        vm.list.splice(1, 2)
-      }).then(() => {
-        expect(vm.$el.innerHTML).toBe('<span>0-e</span><span>1-</span>')
-        vm.list = [{ 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: `
-          <div>
-            <span v-for="(k, v) in obj">{{v}}-{{k}}</span>
-          </div>
-        `,
-        data: {
-          obj: { a: 0, b: 1, c: 2 }
-        }
-      }).$mount()
-      expect(vm.$el.innerHTML).toBe('<span>0-a</span><span>1-b</span><span>2-c</span>')
-      vm.obj.a = 3
-      waitForUpdate(() => {
-        expect(vm.$el.innerHTML).toBe('<span>3-a</span><span>1-b</span><span>2-c</span>')
-        Vue.set(vm.obj, 'd', 4)
-      }).then(() => {
-        expect(vm.$el.innerHTML).toBe('<span>3-a</span><span>1-b</span><span>2-c</span><span>4-d</span>')
-        Vue.delete(vm.obj, 'a')
-      }).then(() => {
-        expect(vm.$el.innerHTML).toBe('<span>1-b</span><span>2-c</span><span>4-d</span>')
-      }).then(done)
-    })
-
-    it('should render each key of data', done => {
-      const vm = new Vue({
-        template: `
-          <div>
-            <span v-for="(k, v) in $data">{{v}}-{{k}}</span>
-          </div>
-        `,
-        data: { a: 0, b: 1, c: 2 }
-      }).$mount()
-      expect(vm.$el.innerHTML).toBe('<span>0-a</span><span>1-b</span><span>2-c</span>')
-      vm.a = 3
-      waitForUpdate(() => {
-        expect(vm.$el.innerHTML).toBe('<span>3-a</span><span>1-b</span><span>2-c</span>')
-      }).then(done)
-    })
-  })
-
   it('check priorities: v-if before v-for', function () {
     const vm = new Vue({
       data: {
@@ -285,8 +291,8 @@ describe('Directive v-for', () => {
       },
       template:
         '<div>' +
-          '<div v-for="(i, item) in items">' +
-            '<p v-for="subItem in item.items">{{$index}} {{subItem.a}} {{i}} {{item.a}}</p>' +
+          '<div v-for="(item, i) in items">' +
+            '<p v-for="(subItem, j) in item.items">{{j}} {{subItem.a}} {{i}} {{item.a}}</p>' +
           '</div>' +
         '</div>'
     }).$mount()

+ 11 - 2
test/unit/modules/compiler/codegen.spec.js

@@ -41,7 +41,16 @@ describe('codegen', () => {
   it('generate v-for directive', () => {
     assertCodegen(
       '<li v-for="item in items" :key="item.uid"></li>',
-      `with(this){return (items)&&_l((items),function(item,$index,$key){return _h(_e('li',{key:item.uid}))})}`
+      `with(this){return (items)&&_l((items),function(item){return _h(_e('li',{key:item.uid}))})}`
+    )
+    // iterator syntax
+    assertCodegen(
+      '<li v-for="(item, i) in items"></li>',
+      `with(this){return (items)&&_l((items),function(item,i){return _h(_e('li'))})}`
+    )
+    assertCodegen(
+      '<li v-for="(item, key, index) in items"></li>',
+      `with(this){return (items)&&_l((items),function(item,key,index){return _h(_e('li'))})}`
     )
   })
 
@@ -69,7 +78,7 @@ describe('codegen', () => {
   it('generate v-ref directive on v-for', () => {
     assertCodegen(
       '<ul><li v-for="item in items" v-ref:component1></li></ul>',
-      `with(this){return _h(_e('ul'),[(items)&&_l((items),function(item,$index,$key){return _h(_e('li',{ref:"component1",refInFor:true}))})])}`
+      `with(this){return _h(_e('ul'),[(items)&&_l((items),function(item){return _h(_e('li',{ref:"component1",refInFor:true}))})])}`
     )
   })
 

+ 12 - 2
test/unit/modules/compiler/parser.spec.js

@@ -108,11 +108,21 @@ describe('parser', () => {
   })
 
   it('v-for directive iteration syntax', () => {
-    const ast = parse('<ul><li v-for="(index, item) in items"></li><ul>', baseOptions)
+    const ast = parse('<ul><li v-for="(item, index) in items"></li><ul>', baseOptions)
     const liAst = ast.children[0]
     expect(liAst.for).toBe('items')
     expect(liAst.alias).toBe('item')
-    expect(liAst.iterator).toBe('index')
+    expect(liAst.iterator1).toBe('index')
+    expect(liAst.iterator2).toBeUndefined()
+  })
+
+  it('v-for directive iteration syntax (multiple)', () => {
+    const ast = parse('<ul><li v-for="(item, key, index) in items"></li><ul>', baseOptions)
+    const liAst = ast.children[0]
+    expect(liAst.for).toBe('items')
+    expect(liAst.alias).toBe('item')
+    expect(liAst.iterator1).toBe('key')
+    expect(liAst.iterator2).toBe('index')
   })
 
   it('v-for directive key', () => {