Browse Source

improve v-repeat strategy check (fix #802)

Evan You 11 years ago
parent
commit
efedd6e6e5
3 changed files with 82 additions and 27 deletions
  1. 39 27
      src/directives/repeat.js
  2. 4 0
      src/instance/init.js
  3. 39 0
      test/unit/specs/directives/repeat_spec.js

+ 39 - 27
src/directives/repeat.js

@@ -39,6 +39,9 @@ module.exports = {
     this.template = this.el.tagName === 'TEMPLATE'
       ? templateParser.parse(this.el, true)
       : this.el
+    // check if we need to use diff instead of inplace
+    // updates
+    this.checkUpdateStrategy()
     // check other directives that need to be handled
     // at v-repeat level
     this.checkIf()
@@ -49,7 +52,38 @@ module.exports = {
       this._checkParam('track-by') ||
       this._checkParam('trackby') // 0.11.0 compat
     this.cache = Object.create(null)
-    this.checkUpdateStrategy()
+  },
+
+  /**
+   * Check what strategy to use for updates.
+   * 
+   * If the repeat is simple enough we can use in-place
+   * updates which simply overwrites existing instances'
+   * data. This strategy reuses DOM nodes and instances
+   * as much as possible.
+   * 
+   * There are two situations where we have to use the
+   * more complex but more accurate diff algorithm:
+   * 1. We are using components with or inside v-repeat.
+   *    The components could have private state that needs
+   *    to be preserved across updates.
+   * 2. We have transitions on the list, which requires
+   *    precise DOM re-positioning.
+   */
+
+  checkUpdateStrategy: function () {
+    var components = Object.keys(this.vm.$options.components)
+    var matcher
+    if (components.length) {
+      matcher = new RegExp(
+        components.map(function (name) {
+          return '<' + name + '(>|\\s)'
+        }).join('|') + '|' + config.prefix + 'component'
+      )
+    }
+    this.needDiff =
+      (matcher && matcher.test(this.template.outerHTML)) ||
+      this.el.hasAttribute(config.prefix + 'transition')
   },
 
   /**
@@ -90,8 +124,10 @@ module.exports = {
     var id = _.attr(this.el, 'component')
     var options = this.vm.$options
     if (!id) {
-      this.Ctor = _.Vue // default constructor
-      this.inherit = true // inline repeats should inherit
+      // default constructor
+      this.Ctor = _.Vue
+      // inline repeats should inherit
+      this.inherit = true
       // important: transclude with no options, just
       // to ensure block start and block end
       this.template = transclude(this.template)
@@ -130,30 +166,6 @@ module.exports = {
     }
   },
 
-  /**
-   * Check what strategy to use for updates.
-   * 
-   * If the repeat is simple enough we can use in-place
-   * updates which simply overwrites existing instances'
-   * data. This strategy reuses DOM nodes and instances
-   * as much as possible.
-   * 
-   * There are two situations where we have to use the
-   * more complex but more accurate diff algorithm:
-   * 1. We are using components with or inside v-repeat.
-   *    The components could have private state that needs
-   *    to be preserved across updates.
-   * 2. We have transitions on the list, which requires
-   *    precise DOM re-positioning.
-   */
-
-  checkUpdateStrategy: function () {
-    this.needDiff =
-      this.asComponent ||
-      this.el.hasAttribute(config.prefix + 'transition') ||
-      this.template.querySelector('[' + config.prefix + 'component]')
-  },
-
   /**
    * Update.
    * This is called whenever the Array mutates.

+ 4 - 0
src/instance/init.js

@@ -58,6 +58,10 @@ exports._init = function (options) {
   // attached/detached hooks on them.
   this._transCpnts = null
 
+  // props used in v-repeat diffing
+  this._new = true
+  this._reused = false
+
   // merge options.
   options = this.$options = mergeOptions(
     this.constructor.options,

+ 39 - 0
test/unit/specs/directives/repeat_spec.js

@@ -718,6 +718,45 @@ if (_.inBrowser) {
       }
     })
 
+    it('should use diff when block contains component', function (done) {
+      var spy = jasmine.createSpy()
+      var obj = { a: 1, b: 2 }
+      var vm = new Vue({
+        el: el,
+        template:
+          '<div v-repeat="list">' +
+            '<test-foo v-with="foo: parentFoo"></test-foo>' +
+            '<div v-component="test-foo" v-with="foo: parentFoo"></div>' +
+          '</div>',
+        data: {
+          list: [1,2]
+        },
+        compiled: function () {
+          this.$set('parentFoo', obj)
+        },
+        components: {
+          'test-foo': {
+            template: '{{foo.a}}',
+            watch: {
+              foo: spy
+            }
+          }
+        }
+      })
+
+      _.nextTick(function () {
+        expect(spy).toHaveBeenCalledWith(obj, undefined)
+        expect(spy.calls.count()).toBe(4)
+        expect(el.innerHTML).toBe([1,2].map(function () {
+          return '<div>' +
+              '<test-foo>1</test-foo><!--v-component-->' +
+              '<div>1</div><!--v-component-->' +
+            '</div>'
+        }).join('') + '<!--v-repeat-->')
+        done()
+      })
+    })
+
   })
 }