Prechádzať zdrojové kódy

v-for: update primitive values update strategy

Evan You 10 rokov pred
rodič
commit
3ff16df808

+ 1 - 0
src/compiler/compile.js

@@ -546,6 +546,7 @@ function makeTerminalNodeLinkFn (el, dirName, value, options, def) {
     name: dirName,
     expression: parsed.expression,
     filters: parsed.filters,
+    raw: value,
     // either an element directive, or if/for
     def: def || publicDirectives[dirName]
   }

+ 28 - 22
src/directives/public/for.js

@@ -109,14 +109,6 @@ module.exports = {
       primitive = !isObject(value)
       frag = !init && this.getCachedFrag(value, i, key)
       if (frag) { // reusable fragment
-
-        if (process.env.NODE_ENV !== 'production' && frag.reused) {
-          _.warn(
-            'Duplicate objects found in v-for="' + this.expression + '": ' +
-            JSON.stringify(value)
-          )
-        }
-
         frag.reused = true
         // update $index
         frag.scope.$index = i
@@ -131,6 +123,7 @@ module.exports = {
         }
       } else { // new isntance
         frag = this.create(value, alias, i, key)
+        frag.fresh = !init
       }
       frags[i] = frag
       if (init) {
@@ -180,7 +173,7 @@ module.exports = {
         // insert with updated stagger index.
         this.insert(frag, insertionIndex++, prevEl, inDoc)
       }
-      frag.reused = false
+      frag.reused = frag.fresh = false
     }
   },
 
@@ -359,13 +352,12 @@ module.exports = {
         ? idKey === '$index'
           ? index
           : value[idKey]
-        : (key || index)
+        : (key || value)
       if (!cache[id]) {
         cache[id] = frag
-      } else if (!primitive && idKey !== '$index') {
-        process.env.NODE_ENV !== 'production' && _.warn(
-          'Duplicate objects with the same track-by key in v-for: ' + id
-        )
+      } else if (idKey !== '$index') {
+        process.env.NODE_ENV !== 'production' &&
+        this.warnDuplicate(value)
       }
     } else {
       id = this.id
@@ -373,10 +365,8 @@ module.exports = {
         if (value[id] === null) {
           value[id] = frag
         } else {
-          process.env.NODE_ENV !== 'production' && _.warn(
-            'Duplicate objects found in v-for="' + this.expression + '": ' +
-            JSON.stringify(value)
-          )
+          process.env.NODE_ENV !== 'production' &&
+          this.warnDuplicate(value)
         }
       } else {
         _.define(value, id, frag)
@@ -397,16 +387,22 @@ module.exports = {
   getCachedFrag: function (value, index, key) {
     var idKey = this.idKey
     var primitive = !isObject(value)
+    var frag
     if (key || idKey || primitive) {
       var id = idKey
         ? idKey === '$index'
           ? index
           : value[idKey]
-        : (key || index)
-      return this.cache[id]
+        : (key || value)
+      frag = this.cache[id]
     } else {
-      return value[this.id]
+      frag = value[this.id]
+    }
+    if (frag && (frag.reused || frag.fresh)) {
+      process.env.NODE_ENV !== 'production' &&
+      this.warnDuplicate(value)
     }
+    return frag
   },
 
   /**
@@ -429,7 +425,7 @@ module.exports = {
         ? idKey === '$index'
           ? index
           : value[idKey]
-        : (key || index)
+        : (key || value)
       this.cache[id] = null
     } else {
       value[this.id] = null
@@ -579,3 +575,13 @@ function range (n) {
   }
   return ret
 }
+
+if (process.env.NODE_ENV !== 'production') {
+  module.exports.warnDuplicate = function (value) {
+    _.warn(
+      'Duplicate value found in v-for="' + this.descriptor.raw + '": ' +
+      JSON.stringify(value) + '. Use track-by="$index" if ' +
+      'you are expecting duplicate values.'
+    )
+  }
+}

+ 84 - 6
test/unit/specs/directives/public/for/for_spec.js

@@ -25,7 +25,7 @@ if (_.inBrowser) {
       var vm = new Vue({
         el: el,
         data: {
-          items: [2, 1, 2]
+          items: [1, 2, 3]
         },
         template: '<div v-for="item in items">{{$index}} {{item}}</div>'
       })
@@ -169,7 +169,7 @@ if (_.inBrowser) {
       var vm = new Vue({
         el: el,
         data: {
-          items: [2, 1, 2]
+          items: [1, 2, 3]
         },
         template: '<test v-for="item in items" :index="$index" :value="item"></test>',
         components: {
@@ -536,6 +536,17 @@ if (_.inBrowser) {
       }
     })
 
+    it('primitive values track by $index', function (done) {
+      var vm = new Vue({
+        el: el,
+        data: {
+          items: [1, 2, 3]
+        },
+        template: '<div v-for="item in items" track-by="$index">{{$index}} {{item}}</div>'
+      })
+      assertPrimitiveMutationsWithDuplicates(vm, el, done)
+    })
+
     it('warn missing alias', function () {
       new Vue({
         el: el,
@@ -553,7 +564,7 @@ if (_.inBrowser) {
           items: [obj, obj]
         }
       })
-      expect(hasWarned(_, 'Duplicate objects')).toBe(true)
+      expect(hasWarned(_, 'Duplicate value')).toBe(true)
     })
 
     it('warn duplicate objects on diff', function (done) {
@@ -568,7 +579,7 @@ if (_.inBrowser) {
       expect(_.warn).not.toHaveBeenCalled()
       vm.items.push(obj)
       _.nextTick(function () {
-        expect(hasWarned(_, 'Duplicate objects')).toBe(true)
+        expect(hasWarned(_, 'Duplicate value')).toBe(true)
         done()
       })
     })
@@ -581,7 +592,7 @@ if (_.inBrowser) {
           items: [{id: 1}, {id: 1}]
         }
       })
-      expect(hasWarned(_, 'Duplicate objects with the same track-by key')).toBe(true)
+      expect(hasWarned(_, 'Duplicate value')).toBe(true)
     })
 
     it('repeat number', function () {
@@ -873,7 +884,74 @@ function assertPrimitiveMutations (vm, el, done) {
   assertMarkup()
   go(
     function () {
-      // check duplicate
+      vm.items.push(4)
+    },
+    assertMarkup
+  )
+  .then(
+    function () {
+      vm.items.shift()
+    },
+    assertMarkup
+  )
+  .then(
+    function () {
+      vm.items.reverse()
+    },
+    assertMarkup
+  )
+  .then(
+    function () {
+      vm.items.pop()
+    },
+    assertMarkup
+  )
+  .then(
+    function () {
+      vm.items.unshift(1)
+    },
+    assertMarkup
+  )
+  .then(
+    function () {
+      vm.items.sort(function (a, b) {
+        return a > b ? 1 : -1
+      })
+    },
+    assertMarkup
+  )
+  .then(
+    function () {
+      vm.items.splice(1, 1, 5)
+    },
+    assertMarkup
+  )
+  // test swapping the array
+  .then(
+    function () {
+      vm.items = [1, 2, 3]
+    },
+    assertMarkup
+  )
+  .run(done)
+
+  function assertMarkup () {
+    var markup = vm.items.map(function (item, i) {
+      return '<div>' + i + ' ' + item + '</div>'
+    }).join('')
+    expect(el.innerHTML).toBe(markup)
+  }
+}
+
+/**
+ * Assert mutation and markup correctness for v-for on
+ * an Array of primitive values when using track-by="$index"
+ */
+
+function assertPrimitiveMutationsWithDuplicates (vm, el, done) {
+  assertMarkup()
+  go(
+    function () {
       vm.items.push(2, 2, 3)
     },
     assertMarkup